Repository: WebKit/WebKit
Pull request #43183: [GTK][WPE][Skia] Record all dirty tiles of a layer once, don't record each tile
Author: login: nikolaszimmermann
Labels: name: WPE WebKit
description: WebKit WPE component
Created: 2025-03-28T08:46:47Z | Merged: 2025-03-31T13:44:57Z

#### 5abe8ab78cbb3a00c4e190faa2b602ed7b199432
<pre>
[GTK][WPE][Skia] Record all dirty tiles of a layer once, don&apos;t record each tile
<a href="https://bugs.webkit.org/show_bug.cgi?id=290614">https://bugs.webkit.org/show_bug.cgi?id=290614</a>

Reviewed by Carlos Garcia Campos.

For each layer, we currently record each dirty tile, then replay in a worker
thread. We can do better and once record a SkPicture of the area spanning all
dirty tiles, and then use the same SkPicture for replaying N times, for each
dirty tile.

Covered by existing tests.

* Source/WebCore/platform/Skia.cmake:
* Source/WebCore/platform/SourcesSkia.txt:
* Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp:
(WebCore::SkiaPaintingEngine::paint):
(WebCore::SkiaPaintingEngine::record):
(WebCore::SkiaPaintingEngine::replay):
(WebCore::SkiaPaintingEngine::paintGraphicsLayerIntoBuffer const): Deleted.
(WebCore::SkiaPaintingEngine::paintLayer): Deleted.
(WebCore::SkiaPaintingEngine::postPaintingTask): Deleted.
(WebCore::SkiaPaintingEngine::performPaintingTask): Deleted.
* Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.h:
(WebCore::SkiaPaintingEngine::useThreadedRendering const):
* Source/WebCore/platform/graphics/skia/SkiaRecordingResult.cpp: Added.
(WebCore::SkiaRecordingResult::SkiaRecordingResult):
(WebCore::SkiaRecordingResult::create):
(WebCore::SkiaRecordingResult::hasFences):
(WebCore::SkiaRecordingResult::waitForFenceIfNeeded):
* Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h: Added.
* Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp:
(WebCore::SkiaReplayCanvas::SkiaReplayCanvas):
(WebCore::SkiaReplayCanvas::create):
(WebCore::SkiaReplayCanvas::waitForRenderingCompletionAndRewrapImageIfNeeded):
* Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.h:
* Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedBackingStoreProxy.cpp:
(WebCore::CoordinatedBackingStoreProxy::updateIfNeeded):
* Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.cpp:
(WebCore::CoordinatedPlatformLayer::paint):
(WebCore::CoordinatedPlatformLayer::record):
(WebCore::CoordinatedPlatformLayer::replay):
* Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.h:

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

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/97497 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/17122 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/7338 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/102586 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/48026 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/99542 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/17415 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/25574 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/74294 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/31474 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/100500 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/13190 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/88171 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/54639 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/12959 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/6053 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/47468 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/82988 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/6134 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/104604 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/24576 "Built successfully") | [  ~~🧪 mac-AS-debug-wk2~~](https://ews-build.webkit.org/#/builders/122/builds/17929 "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/1/builds/83340 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/24948 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/84297 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/82761 "Passed tests") | 
| [✅ 🛠 🧪 merge](https://ews-build.webkit.org/#/builders/19/builds/20819 "Built successfully and passed tests") | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/27303 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/4991 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/18167 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/24538 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/29707 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/24360 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/27674 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/25934 "Built successfully") | | | 
<!--EWS-Status-Bubble-End-->


Diff:
diff --git a/Source/WebCore/platform/Skia.cmake b/Source/WebCore/platform/Skia.cmake
index 5b9e94704cbb..af1d1b5f0694 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/SkiaRecordingResult.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 e6a2c10df749..9a143646e290 100644
--- a/Source/WebCore/platform/SourcesSkia.txt
+++ b/Source/WebCore/platform/SourcesSkia.txt
@@ -61,6 +61,7 @@ platform/graphics/skia/ShareableBitmapSkia.cpp
 platform/graphics/skia/SkiaHarfBuzzFont.cpp
 platform/graphics/skia/SkiaHarfBuzzFontCache.cpp
 platform/graphics/skia/SkiaPaintingEngine.cpp
+platform/graphics/skia/SkiaRecordingResult.cpp
 platform/graphics/skia/SkiaReplayCanvas.cpp
 
 platform/skia/SharedBufferSkia.cpp
diff --git a/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp b/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
index ed460df05674..74b90cd6427b 100644
--- a/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
+++ b/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
@@ -30,19 +30,16 @@
 #include "BitmapTexturePool.h"
 #include "CoordinatedTileBuffer.h"
 #include "GLContext.h"
-#include "GLFence.h"
-#include "GraphicsContextSkia.h"
 #include "GraphicsLayer.h"
 #include "PlatformDisplay.h"
 #include "ProcessCapabilities.h"
 #include "RenderingMode.h"
+#include "SkiaRecordingResult.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>
@@ -106,22 +103,6 @@ void SkiaPaintingEngine::paintIntoGraphicsContext(const GraphicsLayer& layer, Gr
     layer.paintGraphicsLayerContents(context, clipRect);
 }
 
-bool SkiaPaintingEngine::paintGraphicsLayerIntoBuffer(Ref<CoordinatedTileBuffer>& buffer, const GraphicsLayer& layer, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale) const
-{
-    auto* canvas = buffer->canvas();
-    if (!canvas)
-        return false;
-
-    canvas->save();
-    canvas->clear(SkColors::kTransparent);
-
-    GraphicsContextSkia context(*canvas, buffer->isBackedByOpenGL() ? RenderingMode::Accelerated : RenderingMode::Unaccelerated, RenderingPurpose::LayerBacking);
-    paintIntoGraphicsContext(layer, context, dirtyRect, contentsOpaque, contentsScale);
-
-    canvas->restore();
-    return true;
-}
-
 bool SkiaPaintingEngine::isHybridMode() const
 {
     return m_cpuWorkerPool && m_gpuWorkerPool && canPerformAcceleratedRendering();
@@ -227,72 +208,89 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::createBuffer(RenderingMode render
     return CoordinatedUnacceleratedTileBuffer::create(size, contentsOpaque ? CoordinatedTileBuffer::NoFlags : CoordinatedTileBuffer::SupportsAlpha);
 }
 
-Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paintLayer(const GraphicsLayer& layer, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
+Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paint(const GraphicsLayer& layer, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
 {
     // ### Synchronous rendering on main thread ###
-    if (!m_cpuWorkerPool && !m_gpuWorkerPool) {
-        auto renderingMode = canPerformAcceleratedRendering() ? RenderingMode::Accelerated : RenderingMode::Unaccelerated;
-        return performPaintingTask(layer, renderingMode, dirtyRect, contentsOpaque, contentsScale);
-    }
+    ASSERT(!useThreadedRendering());
 
-    // ### Asynchronous rendering on worker threads ###
+    auto renderingMode = canPerformAcceleratedRendering() ? RenderingMode::Accelerated : RenderingMode::Unaccelerated;
 
-    // ### Hybrid CPU/GPU mode ###
-    if (isHybridMode()) {
-        auto renderingMode = decideHybridRenderingMode(dirtyRect, contentsScale);
-        return postPaintingTask(layer, renderingMode, dirtyRect, contentsOpaque, contentsScale);
-    }
+    auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
+    buffer->beginPainting();
 
-    // ### CPU-only mode ###
-    if (m_cpuWorkerPool)
-        return postPaintingTask(layer, RenderingMode::Unaccelerated, dirtyRect, contentsOpaque, contentsScale);
+    if (auto* canvas = buffer->canvas()) {
+        WTFBeginSignpost(canvas, PaintTile, "Skia/%s, dirty region %ix%i+%i+%i", buffer->isBackedByOpenGL() ? "GPU" : "CPU", dirtyRect.x(), dirtyRect.y(), dirtyRect.width(), dirtyRect.height());
+        canvas->save();
+        canvas->clear(SkColors::kTransparent);
 
-    // ### GPU-only mode ###
-    if (m_gpuWorkerPool && canPerformAcceleratedRendering())
-        return postPaintingTask(layer, RenderingMode::Accelerated, dirtyRect, contentsOpaque, contentsScale);
+        GraphicsContextSkia context(*canvas, renderingMode, RenderingPurpose::LayerBacking);
+        paintIntoGraphicsContext(layer, context, dirtyRect, contentsOpaque, contentsScale);
 
-    ASSERT_NOT_REACHED();
-    return performPaintingTask(layer, RenderingMode::Unaccelerated, dirtyRect, contentsOpaque, contentsScale);
+        canvas->restore();
+        WTFEndSignpost(canvas, PaintTile);
+    }
+
+    buffer->completePainting();
+    return buffer;
 }
 
-Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
+Ref<SkiaRecordingResult> SkiaPaintingEngine::record(const GraphicsLayer& layer, const IntRect& recordRect, bool contentsOpaque, float contentsScale)
 {
-    auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
-    buffer->beginPainting();
+    // ### Asynchronous rendering on worker threads ###
+    ASSERT(useThreadedRendering());
+
+    auto renderingMode = canPerformAcceleratedRendering() ? RenderingMode::Accelerated : RenderingMode::Unaccelerated;
 
     WTFBeginSignpost(this, RecordTile);
     SkPictureRecorder pictureRecorder;
-    auto* recordingCanvas = pictureRecorder.beginRecording(dirtyRect.width(), dirtyRect.height());
+    auto* recordingCanvas = pictureRecorder.beginRecording(recordRect.width(), recordRect.height());
     GraphicsContextSkia recordingContext(*recordingCanvas, renderingMode, RenderingPurpose::LayerBacking);
     recordingContext.beginRecording();
-    paintIntoGraphicsContext(layer, recordingContext, dirtyRect, contentsOpaque, contentsScale);
+    paintIntoGraphicsContext(layer, recordingContext, recordRect, contentsOpaque, contentsScale);
     auto imageToFenceMap = recordingContext.endRecording();
     auto picture = pictureRecorder.finishRecordingAsPicture();
     WTFEndSignpost(this, RecordTile);
 
+    return SkiaRecordingResult::create(WTFMove(picture), WTFMove(imageToFenceMap), recordRect, renderingMode, contentsOpaque, contentsScale);
+}
+
+Ref<CoordinatedTileBuffer> SkiaPaintingEngine::replay(const RefPtr<SkiaRecordingResult>& recording, const IntRect& dirtyRect)
+{
+    // ### Asynchronous rendering on worker threads ###
+    ASSERT(useThreadedRendering());
+
+    auto renderingMode = recording->renderingMode();
+    if (renderingMode == RenderingMode::Accelerated && !recording->hasFences() && isHybridMode())
+        renderingMode = decideHybridRenderingMode(dirtyRect, recording->contentsScale());
+
+    auto buffer = createBuffer(renderingMode, dirtyRect.size(), recording->contentsOpaque());
+    buffer->beginPainting();
+
     auto& workerPool = renderingMode == RenderingMode::Accelerated ? *m_gpuWorkerPool.get() : *m_cpuWorkerPool.get();
-    workerPool.postTask([buffer = Ref { buffer }, picture = sk_sp<SkPicture> { picture }, dirtyRect, imageToFenceMap = WTFMove(imageToFenceMap)]() mutable {
+    workerPool.postTask([buffer = Ref { buffer }, dirtyRect, recording = RefPtr { recording }]() mutable {
         auto* canvas = buffer->canvas();
         if (!canvas) {
             buffer->completePainting();
             return;
         }
 
-        auto replayPicture = [&picture](SkCanvas* canvas) {
+        auto replayPicture = [](const sk_sp<SkPicture>& picture, SkCanvas* canvas, const IntRect& recordRect, const IntRect& paintRect) {
             canvas->save();
             canvas->clear(SkColors::kTransparent);
+            canvas->clipRect(SkRect::MakeXYWH(0, 0, paintRect.width(), paintRect.height()));
+            canvas->translate(recordRect.x() - paintRect.x(), recordRect.y() - paintRect.y());
             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));
+        if (recording->hasFences()) {
+            auto replayCanvas = SkiaReplayCanvas::create(dirtyRect.size(), recording);
             replayCanvas->addCanvas(canvas);
-            replayPicture(&replayCanvas.get());
+            replayPicture(replayCanvas->picture(), &replayCanvas.get(), recording->recordRect(), dirtyRect);
             replayCanvas->removeCanvas(canvas);
         } else
-            replayPicture(canvas);
+            replayPicture(recording->picture(), canvas, recording->recordRect(), dirtyRect);
         WTFEndSignpost(canvas, PaintTile);
 
         buffer->completePainting();
@@ -301,21 +299,6 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLa
     return buffer;
 }
 
-Ref<CoordinatedTileBuffer> SkiaPaintingEngine::performPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
-{
-    auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
-    buffer->beginPainting();
-
-    if (auto* canvas = buffer->canvas()) {
-        WTFBeginSignpost(canvas, PaintTile, "Skia/%s, dirty region %ix%i+%i+%i", buffer->isBackedByOpenGL() ? "GPU" : "CPU", dirtyRect.x(), dirtyRect.y(), dirtyRect.width(), dirtyRect.height());
-        paintGraphicsLayerIntoBuffer(buffer, layer, dirtyRect, contentsOpaque, contentsScale);
-        WTFEndSignpost(canvas, PaintTile);
-    }
-
-    buffer->completePainting();
-    return buffer;
-}
-
 unsigned SkiaPaintingEngine::numberOfCPUPaintingThreads()
 {
     static std::once_flag onceFlag;
diff --git a/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.h b/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.h
index 295dc5e3954a..31ed9dea56b0 100644
--- a/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.h
+++ b/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.h
@@ -30,8 +30,6 @@
 #include <wtf/TZoneMalloc.h>
 #include <wtf/WorkerPool.h>
 
-class SkImage;
-
 namespace WebCore {
 
 class BitmapTexturePool;
@@ -40,6 +38,7 @@ class GraphicsContext;
 class GraphicsLayer;
 class IntRect;
 class IntSize;
+class SkiaRecordingResult;
 enum class RenderingMode : uint8_t;
 
 class SkiaPaintingEngine {
@@ -66,18 +65,15 @@ class SkiaPaintingEngine {
     static float minimumFractionOfTasksUsingGPUPainting();
     static HybridPaintingStrategy hybridPaintingStrategy();
 
-    Ref<CoordinatedTileBuffer> paintLayer(const GraphicsLayer&, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale);
+    bool useThreadedRendering() const { return m_cpuWorkerPool || m_gpuWorkerPool; }
+
+    Ref<CoordinatedTileBuffer> paint(const GraphicsLayer&, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale);
+    Ref<SkiaRecordingResult> record(const GraphicsLayer&, const IntRect& recordRect, bool contentsOpaque, float contentsScale);
+    Ref<CoordinatedTileBuffer> replay(const RefPtr<SkiaRecordingResult>&, const IntRect& dirtyRect);
 
 private:
     Ref<CoordinatedTileBuffer> createBuffer(RenderingMode, const IntSize&, bool contentsOpaque) const;
     void paintIntoGraphicsContext(const GraphicsLayer&, GraphicsContext&, const IntRect&, bool contentsOpaque, float contentsScale) const;
-    bool paintGraphicsLayerIntoBuffer(Ref<CoordinatedTileBuffer>&, const GraphicsLayer&, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale) const;
-
-    // Threaded rendering
-    Ref<CoordinatedTileBuffer> postPaintingTask(const GraphicsLayer&, RenderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale);
-
-    // Main thread rendering
-    Ref<CoordinatedTileBuffer> performPaintingTask(const GraphicsLayer&, RenderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale);
 
     bool isHybridMode() const;
     RenderingMode decideHybridRenderingMode(const IntRect& dirtyRect, float contentsScale) const;
diff --git a/Source/WebCore/platform/graphics/skia/SkiaRecordingResult.cpp b/Source/WebCore/platform/graphics/skia/SkiaRecordingResult.cpp
new file mode 100644
index 000000000000..cbcc14d9aaa9
--- /dev/null
+++ b/Source/WebCore/platform/graphics/skia/SkiaRecordingResult.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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 "SkiaRecordingResult.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+
+namespace WebCore {
+
+SkiaRecordingResult::SkiaRecordingResult(sk_sp<SkPicture>&& picture, SkiaImageToFenceMap&& imageToFenceMap, const IntRect& recordRect, RenderingMode renderingMode, bool contentsOpaque, float contentsScale)
+    : m_picture(WTFMove(picture))
+    , m_imageToFenceMap(WTFMove(imageToFenceMap))
+    , m_recordRect(recordRect)
+    , m_renderingMode(renderingMode)
+    , m_contentsOpaque(contentsOpaque)
+    , m_contentsScale(contentsScale)
+{
+}
+
+SkiaRecordingResult::~SkiaRecordingResult() = default;
+
+Ref<SkiaRecordingResult> SkiaRecordingResult::create(sk_sp<SkPicture>&& picture, SkiaImageToFenceMap&& imageToFenceMap, const IntRect& recordRect, RenderingMode renderingMode, bool contentsOpaque, float contentsScale)
+{
+    return adoptRef(*new SkiaRecordingResult(WTFMove(picture), WTFMove(imageToFenceMap), recordRect, renderingMode, contentsOpaque, contentsScale));
+}
+
+bool SkiaRecordingResult::hasFences()
+{
+    Locker locker { m_imageToFenceMapLock };
+    return !m_imageToFenceMap.isEmpty();
+}
+
+void SkiaRecordingResult::waitForFenceIfNeeded(const SkImage& image)
+{
+    Locker locker { m_imageToFenceMapLock };
+    if (auto fence = m_imageToFenceMap.get(&image))
+        fence->serverWait();
+}
+
+} // namespace WebCore
+
+#endif // USE(COORDINATED_GRAPHICS) && USE(SKIA)
diff --git a/Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h b/Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h
new file mode 100644
index 000000000000..fc371d84ab5b
--- /dev/null
+++ b/Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h
@@ -0,0 +1,70 @@
+/*
+ * 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"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/core/SkPicture.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+#include <wtf/Lock.h>
+#include <wtf/ThreadSafeRefCounted.h>
+
+class SkImage;
+
+namespace WebCore {
+
+class SkiaRecordingResult final : public ThreadSafeRefCounted<SkiaRecordingResult> {
+public:
+    virtual ~SkiaRecordingResult();
+    static Ref<SkiaRecordingResult> create(sk_sp<SkPicture>&&, SkiaImageToFenceMap&&, const IntRect& recordRect, RenderingMode, bool contentsOpaque, float contentsScale);
+
+    void waitForFenceIfNeeded(const SkImage&);
+    bool hasFences();
+
+    const sk_sp<SkPicture>& picture() const { return m_picture; }
+    const IntRect& recordRect() const { return m_recordRect; }
+    RenderingMode renderingMode() const { return m_renderingMode; }
+    bool contentsOpaque() const { return m_contentsOpaque; }
+    float contentsScale() const { return m_contentsScale; }
+
+private:
+    SkiaRecordingResult(sk_sp<SkPicture>&&, SkiaImageToFenceMap&&, const IntRect& recordRect, RenderingMode, bool contentsOpaque, float contentsScale);
+
+    sk_sp<SkPicture> m_picture;
+    SkiaImageToFenceMap m_imageToFenceMap WTF_GUARDED_BY_LOCK(m_imageToFenceMapLock);
+    Lock m_imageToFenceMapLock;
+    IntRect m_recordRect;
+    RenderingMode m_renderingMode { RenderingMode::Unaccelerated };
+    bool m_contentsOpaque : 1 { true };
+    float m_contentsScale { 0 };
+};
+
+} // namespace WebCore
+
+#endif // USE(COORDINATED_GRAPHICS) && USE(SKIA)
diff --git a/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp b/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
index 6869f20eb827..f8ac23b1b06e 100644
--- a/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
+++ b/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
@@ -38,19 +38,17 @@ WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
 
 namespace WebCore {
 
-WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaReplayCanvas);
-
-SkiaReplayCanvas::SkiaReplayCanvas(const IntSize& size, SkiaImageToFenceMap&& imageToFenceMap)
+SkiaReplayCanvas::SkiaReplayCanvas(const IntSize& size, const RefPtr<SkiaRecordingResult>& recording)
     : SkNWayCanvas(size.width(), size.height())
-    , m_imageToFenceMap(WTFMove(imageToFenceMap))
+    , m_recording(recording)
 {
 }
 
 SkiaReplayCanvas::~SkiaReplayCanvas() = default;
 
-Ref<SkiaReplayCanvas> SkiaReplayCanvas::create(const IntSize& size, SkiaImageToFenceMap&& imageToFenceMap)
+Ref<SkiaReplayCanvas> SkiaReplayCanvas::create(const IntSize& size, const RefPtr<SkiaRecordingResult>& recodingResult)
 {
-    return adoptRef(*new SkiaReplayCanvas(size, WTFMove(imageToFenceMap)));
+    return adoptRef(*new SkiaReplayCanvas(size, recodingResult));
 }
 
 sk_sp<SkImage> SkiaReplayCanvas::waitForRenderingCompletionAndRewrapImageIfNeeded(const SkImage* image)
@@ -62,8 +60,7 @@ sk_sp<SkImage> SkiaReplayCanvas::waitForRenderingCompletionAndRewrapImageIfNeede
     if (!glContext || !glContext->makeContextCurrent())
         return nullptr;
 
-    if (auto fence = m_imageToFenceMap.take(image))
-        fence->serverWait();
+    m_recording->waitForFenceIfNeeded(*image);
 
     auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
     if (image->isValid(grContext))
diff --git a/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.h b/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.h
index 060aa45cc2a6..fb4515a57d7f 100644
--- a/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.h
+++ b/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.h
@@ -26,30 +26,25 @@
 #pragma once
 
 #if USE(COORDINATED_GRAPHICS) && USE(SKIA)
-#include "GraphicsContextSkia.h"
 #include "IntSize.h"
+#include "SkiaRecordingResult.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&&);
+    static Ref<SkiaReplayCanvas> create(const IntSize&, const RefPtr<SkiaRecordingResult>&);
+
+    const sk_sp<SkPicture>& picture() const { return m_recording->picture(); }
 
 private:
-    SkiaReplayCanvas(const IntSize&, SkiaImageToFenceMap&&);
+    SkiaReplayCanvas(const IntSize&, const RefPtr<SkiaRecordingResult>&);
 
     sk_sp<SkImage> waitForRenderingCompletionAndRewrapImageIfNeeded(const SkImage*);
 
@@ -80,7 +75,7 @@ class SkiaReplayCanvas final : public SkNWayCanvas, public RefCounted<SkiaReplay
     void onDrawTextBlob(const SkTextBlob*, SkScalar x, SkScalar y, const SkPaint&) override;
     void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
 
-    SkiaImageToFenceMap m_imageToFenceMap;
+    RefPtr<SkiaRecordingResult> m_recording;
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedBackingStoreProxy.cpp b/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedBackingStoreProxy.cpp
index bd4c3e9a20f5..97bb05ab2e8c 100644
--- a/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedBackingStoreProxy.cpp
+++ b/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedBackingStoreProxy.cpp
@@ -28,6 +28,11 @@
 #include <wtf/SystemTracing.h>
 #include <wtf/TZoneMallocInlines.h>
 
+#if USE(SKIA)
+#include "SkiaPaintingEngine.h"
+#include "SkiaRecordingResult.h"
+#endif
+
 namespace WebCore {
 
 WTF_MAKE_TZONE_ALLOCATED_IMPL(CoordinatedBackingStoreProxy);
@@ -148,14 +153,25 @@ OptionSet<CoordinatedBackingStoreProxy::UpdateResult> CoordinatedBackingStorePro
         result.add(UpdateResult::TilesPending);
 
     // Update the dirty tiles.
+    IntRect tileDirtyRectUnion;
     unsigned dirtyTilesCount = 0;
     for (const auto& tile : m_tiles.values()) {
-        if (tile.isDirty())
-            dirtyTilesCount++;
+        if (!tile.isDirty())
+            continue;
+
+        tileDirtyRectUnion.unite(tile.dirtyRect);
+        ++dirtyTilesCount;
     }
 
     WTFBeginSignpost(this, UpdateTiles, "dirty tiles: %u", dirtyTilesCount);
 
+#if USE(SKIA)
+    // Record only once the whole layer.
+    RefPtr<SkiaRecordingResult> recording;
+    if (LIKELY(dirtyTilesCount > 0 && layer.client().paintingEngine().useThreadedRendering()))
+        recording = layer.record(tileDirtyRectUnion);
+#endif
+
     Vector<Update::TileUpdate> tilesToUpdate;
     unsigned dirtyTileIndex = 0;
     for (auto& tile : m_tiles.values()) {
@@ -165,7 +181,12 @@ OptionSet<CoordinatedBackingStoreProxy::UpdateResult> CoordinatedBackingStorePro
         WTFBeginSignpost(this, UpdateTile, "%u/%u, id: %d, rect: %ix%i+%i+%i, dirty: %ix%i+%i+%i", ++dirtyTileIndex, dirtyTilesCount, tile.id,
             tile.rect.x(), tile.rect.y(), tile.rect.width(), tile.rect.height(), tile.dirtyRect.x(), tile.dirtyRect.y(), tile.dirtyRect.width(), tile.dirtyRect.height());
 
+#if USE(SKIA)
+        auto buffer = recording ? layer.replay(recording, tile.dirtyRect) : layer.paint(tile.dirtyRect);
+#else
         auto buffer = layer.paint(tile.dirtyRect);
+#endif
+
         IntRect updateRect(tile.dirtyRect);
         updateRect.move(-tile.rect.x(), -tile.rect.y());
         tilesToUpdate.append({ tile.id, tile.rect, WTFMove(updateRect), WTFMove(buffer) });
diff --git a/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.cpp b/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.cpp
index a8bf087451d4..e116abb8b6f1 100644
--- a/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.cpp
+++ b/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.cpp
@@ -47,6 +47,7 @@
 
 #if USE(SKIA)
 #include "SkiaPaintingEngine.h"
+#include "SkiaRecordingResult.h"
 #endif
 
 namespace WebCore {
@@ -818,10 +819,29 @@ Ref<CoordinatedTileBuffer> CoordinatedPlatformLayer::paint(const IntRect& dirtyR
     m_client->paintingEngine().paint(*m_owner, buffer.get(), dirtyRect, enclosingIntRect(scaledDirtyRect), IntRect { { }, dirtyRect.size() }, m_contentsScale);
     return buffer;
 #elif USE(SKIA)
-    return m_client->paintingEngine().paintLayer(*m_owner, dirtyRect, m_contentsOpaque, m_contentsScale);
+    auto& paintingEngine = m_client->paintingEngine();
+    ASSERT(!paintingEngine.useThreadedRendering());
+    return m_client->paintingEngine().paint(*m_owner, dirtyRect, m_contentsOpaque, m_contentsScale);
 #endif
 }
 
+#if USE(SKIA)
+Ref<SkiaRecordingResult> CoordinatedPlatformLayer::record(const IntRect& recordRect)
+{
+    auto& paintingEngine = m_client->paintingEngine();
+    ASSERT(paintingEngine.useThreadedRendering());
+    return m_client->paintingEngine().record(*m_owner, recordRect, m_contentsOpaque, m_contentsScale);
+}
+
+Ref<CoordinatedTileBuffer> CoordinatedPlatformLayer::replay(const RefPtr<SkiaRecordingResult>& recording, const IntRect& dirtyRect)
+{
+    ASSERT(recording);
+    auto& paintingEngine = m_client->paintingEngine();
+    ASSERT(paintingEngine.useThreadedRendering());
+    return m_client->paintingEngine().replay(recording, dirtyRect);
+}
+#endif
+
 void CoordinatedPlatformLayer::waitUntilPaintingComplete()
 {
     Locker locker { m_lock };
diff --git a/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.h b/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.h
index e9a3f26a974b..d7e87e02a102 100644
--- a/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.h
+++ b/Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedPlatformLayer.h
@@ -51,6 +51,7 @@ class TextureMapperLayer;
 
 #if USE(SKIA)
 class SkiaPaintingEngine;
+class SkiaRecordingResult;
 #endif
 #if USE(CAIRO)
 namespace Cairo {
@@ -182,6 +183,10 @@ class CoordinatedPlatformLayer : public ThreadSafeRefCounted<CoordinatedPlatform
     RunLoop* compositingRunLoop() const;
 
     Ref<CoordinatedTileBuffer> paint(const IntRect&);
+#if USE(SKIA)
+    Ref<SkiaRecordingResult> record(const IntRect&);
+    Ref<CoordinatedTileBuffer> replay(const RefPtr<SkiaRecordingResult>&, const IntRect&);
+#endif
     void waitUntilPaintingComplete();
 
 private:


Reviews:
user: login: carlosgcampos
body: Awesome!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T09:10:42Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T12:17:34Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T12:18:02Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T12:18:15Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T21:23:03Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T21:26:26Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T21:30:54Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T21:31:57Z
user: login: carlosgcampos
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T12:10:31Z

Review comments:
diff_hunk: @@ -0,0 +1,62 @@
+/*
+ * 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 "SkiaRecordingResult.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaRecordingResult);
+
+SkiaRecordingResult::SkiaRecordingResult(sk_sp<SkPicture>&& picture, SkiaImageToFenceMap&& imageToFenceMap, const IntRect& recordRect, RenderingMode renderingMode, bool contentsOpaque, float contentsScale)
+    : m_picture(WTFMove(picture))
+    , m_imageToFenceMap(WTFMove(imageToFenceMap))
+    , m_recordRect(recordRect)
+    , m_renderingMode(renderingMode)
+    , m_contentsOpaque(contentsOpaque)
+    , m_contentsScale(contentsScale)
+{
+}
+
+SkiaRecordingResult::~SkiaRecordingResult() = default;
+
+Ref<SkiaRecordingResult> SkiaRecordingResult::create(sk_sp<SkPicture>&& picture, SkiaImageToFenceMap&& imageToFenceMap, const IntRect& recordRect, RenderingMode renderingMode, bool contentsOpaque, float contentsScale)
+{
+    return adoptRef(*new SkiaRecordingResult(WTFMove(picture), WTFMove(imageToFenceMap), recordRect, renderingMode, contentsOpaque, contentsScale));
+}
+
+void SkiaRecordingResult::waitForFenceIfNeeded(const SkImage* image)
+{
+    ASSERT(image);
+
+    if (auto fence = m_imageToFenceMap.take(image))
+        fence->serverWait();
path: Source/WebCore/platform/graphics/skia/SkiaRecordingResult.cpp
user: login: carlosgcampos
body: We need a lock here to access the fence map. I think this approach of the first thread waits for the fence works for sure with a client wait, but I'm not sure with a server wait.
created_at: 2025-03-28T08:57:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,71 @@
+/*
+ * 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"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/core/SkPicture.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+#include <wtf/TZoneMalloc.h>
+#include <wtf/ThreadSafeRefCounted.h>
+
+class SkImage;
+
+namespace WebCore {
+
+class SkiaRecordingResult final : public ThreadSafeRefCounted<SkiaRecordingResult> {
+    WTF_MAKE_TZONE_ALLOCATED(SkiaRecordingResult);
+    WTF_MAKE_NONCOPYABLE(SkiaRecordingResult);
path: Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h
user: login: carlosgcampos
body: I don't think we need this for refcounted classes.
created_at: 2025-03-28T08:58:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,62 @@
+/*
+ * 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 "SkiaRecordingResult.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaRecordingResult);
+
+SkiaRecordingResult::SkiaRecordingResult(sk_sp<SkPicture>&& picture, SkiaImageToFenceMap&& imageToFenceMap, const IntRect& recordRect, RenderingMode renderingMode, bool contentsOpaque, float contentsScale)
+    : m_picture(WTFMove(picture))
+    , m_imageToFenceMap(WTFMove(imageToFenceMap))
+    , m_recordRect(recordRect)
+    , m_renderingMode(renderingMode)
+    , m_contentsOpaque(contentsOpaque)
+    , m_contentsScale(contentsScale)
+{
+}
+
+SkiaRecordingResult::~SkiaRecordingResult() = default;
+
+Ref<SkiaRecordingResult> SkiaRecordingResult::create(sk_sp<SkPicture>&& picture, SkiaImageToFenceMap&& imageToFenceMap, const IntRect& recordRect, RenderingMode renderingMode, bool contentsOpaque, float contentsScale)
+{
+    return adoptRef(*new SkiaRecordingResult(WTFMove(picture), WTFMove(imageToFenceMap), recordRect, renderingMode, contentsOpaque, contentsScale));
+}
+
+void SkiaRecordingResult::waitForFenceIfNeeded(const SkImage* image)
+{
+    ASSERT(image);
path: Source/WebCore/platform/graphics/skia/SkiaRecordingResult.cpp
user: login: carlosgcampos
body: Let's pass a reference then
created_at: 2025-03-28T08:58:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,71 @@
+/*
+ * 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"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/core/SkPicture.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+#include <wtf/TZoneMalloc.h>
+#include <wtf/ThreadSafeRefCounted.h>
+
+class SkImage;
+
+namespace WebCore {
+
+class SkiaRecordingResult final : public ThreadSafeRefCounted<SkiaRecordingResult> {
+    WTF_MAKE_TZONE_ALLOCATED(SkiaRecordingResult);
+    WTF_MAKE_NONCOPYABLE(SkiaRecordingResult);
+public:
+    virtual ~SkiaRecordingResult();
+    static Ref<SkiaRecordingResult> create(sk_sp<SkPicture>&&, SkiaImageToFenceMap&&, const IntRect& recordRect, RenderingMode, bool contentsOpaque, float contentsScale);
+
+    void waitForFenceIfNeeded(const SkImage*);
+
+    bool hasFences() const { return !m_imageToFenceMap.isEmpty(); }
path: Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h
user: login: carlosgcampos
body: We need a lock here too, all accesses to the map need to be protected by a lock.
created_at: 2025-03-28T08:59:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,71 @@
+/*
+ * 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"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/core/SkPicture.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+#include <wtf/TZoneMalloc.h>
+#include <wtf/ThreadSafeRefCounted.h>
+
+class SkImage;
+
+namespace WebCore {
+
+class SkiaRecordingResult final : public ThreadSafeRefCounted<SkiaRecordingResult> {
+    WTF_MAKE_TZONE_ALLOCATED(SkiaRecordingResult);
+    WTF_MAKE_NONCOPYABLE(SkiaRecordingResult);
+public:
+    virtual ~SkiaRecordingResult();
+    static Ref<SkiaRecordingResult> create(sk_sp<SkPicture>&&, SkiaImageToFenceMap&&, const IntRect& recordRect, RenderingMode, bool contentsOpaque, float contentsScale);
+
+    void waitForFenceIfNeeded(const SkImage*);
+
+    bool hasFences() const { return !m_imageToFenceMap.isEmpty(); }
+    const sk_sp<SkPicture>& picture() const { return m_picture; }
+    IntRect recordRect() const { return m_recordRect; }
path: Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h
user: login: carlosgcampos
body: ```suggestion
    const IntRect& recordRect() const { return m_recordRect; }
```
created_at: 2025-03-28T08:59:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,14 +153,25 @@ OptionSet<CoordinatedBackingStoreProxy::UpdateResult> CoordinatedBackingStorePro
         result.add(UpdateResult::TilesPending);
 
     // Update the dirty tiles.
+    IntRect tileDirtyRectUnion;
     unsigned dirtyTilesCount = 0;
     for (const auto& tile : m_tiles.values()) {
-        if (tile.isDirty())
-            dirtyTilesCount++;
+        if (!tile.isDirty())
+            continue;
+
+        tileDirtyRectUnion.unite(tile.dirtyRect);
+        ++dirtyTilesCount;
     }
 
     WTFBeginSignpost(this, UpdateTiles, "dirty tiles: %u", dirtyTilesCount);
 
+#if USE(SKIA)
+    // Record only once the whole layer.
+    std::optional<Ref<SkiaRecordingResult>> recording;
path: Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedBackingStoreProxy.cpp
user: login: carlosgcampos
body: I would use RefPtr instead of optional Ref
created_at: 2025-03-28T09:02:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,14 +153,25 @@ OptionSet<CoordinatedBackingStoreProxy::UpdateResult> CoordinatedBackingStorePro
         result.add(UpdateResult::TilesPending);
 
     // Update the dirty tiles.
+    IntRect tileDirtyRectUnion;
     unsigned dirtyTilesCount = 0;
     for (const auto& tile : m_tiles.values()) {
-        if (tile.isDirty())
-            dirtyTilesCount++;
+        if (!tile.isDirty())
+            continue;
+
+        tileDirtyRectUnion.unite(tile.dirtyRect);
+        ++dirtyTilesCount;
     }
 
     WTFBeginSignpost(this, UpdateTiles, "dirty tiles: %u", dirtyTilesCount);
 
+#if USE(SKIA)
+    // Record only once the whole layer.
+    std::optional<Ref<SkiaRecordingResult>> recording;
+    if (LIKELY(dirtyTilesCount > 0 && layer.client().paintingEngine().useThreadedRendering()))
path: Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedBackingStoreProxy.cpp
user: login: carlosgcampos
body: I don't like that the backing store proxy needs to know about threaded rendering, but don't know a better solution. The other option would be that the paint engine knows about tiles, and that's not better.
created_at: 2025-03-28T09:07:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,71 @@
+/*
+ * 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"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/core/SkPicture.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+#include <wtf/TZoneMalloc.h>
+#include <wtf/ThreadSafeRefCounted.h>
+
+class SkImage;
+
+namespace WebCore {
+
+class SkiaRecordingResult final : public ThreadSafeRefCounted<SkiaRecordingResult> {
+    WTF_MAKE_TZONE_ALLOCATED(SkiaRecordingResult);
+    WTF_MAKE_NONCOPYABLE(SkiaRecordingResult);
+public:
+    virtual ~SkiaRecordingResult();
+    static Ref<SkiaRecordingResult> create(sk_sp<SkPicture>&&, SkiaImageToFenceMap&&, const IntRect& recordRect, RenderingMode, bool contentsOpaque, float contentsScale);
+
+    void waitForFenceIfNeeded(const SkImage*);
+
+    bool hasFences() const { return !m_imageToFenceMap.isEmpty(); }
+    const sk_sp<SkPicture>& picture() const { return m_picture; }
+    IntRect recordRect() const { return m_recordRect; }
+    RenderingMode renderingMode() const { return m_renderingMode; }
path: Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h
user: login: carlosgcampos
body: Is this actually used?
created_at: 2025-03-28T09:10:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,14 +153,25 @@ OptionSet<CoordinatedBackingStoreProxy::UpdateResult> CoordinatedBackingStorePro
         result.add(UpdateResult::TilesPending);
 
     // Update the dirty tiles.
+    IntRect tileDirtyRectUnion;
     unsigned dirtyTilesCount = 0;
     for (const auto& tile : m_tiles.values()) {
-        if (tile.isDirty())
-            dirtyTilesCount++;
+        if (!tile.isDirty())
+            continue;
+
+        tileDirtyRectUnion.unite(tile.dirtyRect);
+        ++dirtyTilesCount;
     }
 
     WTFBeginSignpost(this, UpdateTiles, "dirty tiles: %u", dirtyTilesCount);
 
+#if USE(SKIA)
+    // Record only once the whole layer.
+    std::optional<Ref<SkiaRecordingResult>> recording;
path: Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedBackingStoreProxy.cpp
user: login: nikolaszimmermann
body: Fair enough.
created_at: 2025-03-28T12:17:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,71 @@
+/*
+ * 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"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/core/SkPicture.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+#include <wtf/TZoneMalloc.h>
+#include <wtf/ThreadSafeRefCounted.h>
+
+class SkImage;
+
+namespace WebCore {
+
+class SkiaRecordingResult final : public ThreadSafeRefCounted<SkiaRecordingResult> {
+    WTF_MAKE_TZONE_ALLOCATED(SkiaRecordingResult);
+    WTF_MAKE_NONCOPYABLE(SkiaRecordingResult);
+public:
+    virtual ~SkiaRecordingResult();
+    static Ref<SkiaRecordingResult> create(sk_sp<SkPicture>&&, SkiaImageToFenceMap&&, const IntRect& recordRect, RenderingMode, bool contentsOpaque, float contentsScale);
+
+    void waitForFenceIfNeeded(const SkImage*);
+
+    bool hasFences() const { return !m_imageToFenceMap.isEmpty(); }
+    const sk_sp<SkPicture>& picture() const { return m_picture; }
+    IntRect recordRect() const { return m_recordRect; }
path: Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h
user: login: nikolaszimmermann
body: Ok. I usually don't do that for almost-POD types, but it's fine. will adapt.
created_at: 2025-03-28T12:18:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,71 @@
+/*
+ * 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"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/core/SkPicture.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+#include <wtf/TZoneMalloc.h>
+#include <wtf/ThreadSafeRefCounted.h>
+
+class SkImage;
+
+namespace WebCore {
+
+class SkiaRecordingResult final : public ThreadSafeRefCounted<SkiaRecordingResult> {
+    WTF_MAKE_TZONE_ALLOCATED(SkiaRecordingResult);
+    WTF_MAKE_NONCOPYABLE(SkiaRecordingResult);
+public:
+    virtual ~SkiaRecordingResult();
+    static Ref<SkiaRecordingResult> create(sk_sp<SkPicture>&&, SkiaImageToFenceMap&&, const IntRect& recordRect, RenderingMode, bool contentsOpaque, float contentsScale);
+
+    void waitForFenceIfNeeded(const SkImage*);
+
+    bool hasFences() const { return !m_imageToFenceMap.isEmpty(); }
path: Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h
user: login: nikolaszimmermann
body: Damn, indeed, that's missing. Will add.
created_at: 2025-03-28T12:18:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,62 @@
+/*
+ * 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 "SkiaRecordingResult.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaRecordingResult);
+
+SkiaRecordingResult::SkiaRecordingResult(sk_sp<SkPicture>&& picture, SkiaImageToFenceMap&& imageToFenceMap, const IntRect& recordRect, RenderingMode renderingMode, bool contentsOpaque, float contentsScale)
+    : m_picture(WTFMove(picture))
+    , m_imageToFenceMap(WTFMove(imageToFenceMap))
+    , m_recordRect(recordRect)
+    , m_renderingMode(renderingMode)
+    , m_contentsOpaque(contentsOpaque)
+    , m_contentsScale(contentsScale)
+{
+}
+
+SkiaRecordingResult::~SkiaRecordingResult() = default;
+
+Ref<SkiaRecordingResult> SkiaRecordingResult::create(sk_sp<SkPicture>&& picture, SkiaImageToFenceMap&& imageToFenceMap, const IntRect& recordRect, RenderingMode renderingMode, bool contentsOpaque, float contentsScale)
+{
+    return adoptRef(*new SkiaRecordingResult(WTFMove(picture), WTFMove(imageToFenceMap), recordRect, renderingMode, contentsOpaque, contentsScale));
+}
+
+void SkiaRecordingResult::waitForFenceIfNeeded(const SkImage* image)
+{
+    ASSERT(image);
+
+    if (auto fence = m_imageToFenceMap.take(image))
+        fence->serverWait();
path: Source/WebCore/platform/graphics/skia/SkiaRecordingResult.cpp
user: login: nikolaszimmermann
body: Locking is fixed -- let's try EWS with server wait.
created_at: 2025-03-30T21:23:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,71 @@
+/*
+ * 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"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/core/SkPicture.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+#include <wtf/TZoneMalloc.h>
+#include <wtf/ThreadSafeRefCounted.h>
+
+class SkImage;
+
+namespace WebCore {
+
+class SkiaRecordingResult final : public ThreadSafeRefCounted<SkiaRecordingResult> {
+    WTF_MAKE_TZONE_ALLOCATED(SkiaRecordingResult);
+    WTF_MAKE_NONCOPYABLE(SkiaRecordingResult);
path: Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h
user: login: nikolaszimmermann
body: Ok. We did the same for SkiaReplayCanvas, will fix it as well.
created_at: 2025-03-30T21:26:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,14 +153,25 @@ OptionSet<CoordinatedBackingStoreProxy::UpdateResult> CoordinatedBackingStorePro
         result.add(UpdateResult::TilesPending);
 
     // Update the dirty tiles.
+    IntRect tileDirtyRectUnion;
     unsigned dirtyTilesCount = 0;
     for (const auto& tile : m_tiles.values()) {
-        if (tile.isDirty())
-            dirtyTilesCount++;
+        if (!tile.isDirty())
+            continue;
+
+        tileDirtyRectUnion.unite(tile.dirtyRect);
+        ++dirtyTilesCount;
     }
 
     WTFBeginSignpost(this, UpdateTiles, "dirty tiles: %u", dirtyTilesCount);
 
+#if USE(SKIA)
+    // Record only once the whole layer.
+    std::optional<Ref<SkiaRecordingResult>> recording;
+    if (LIKELY(dirtyTilesCount > 0 && layer.client().paintingEngine().useThreadedRendering()))
path: Source/WebCore/platform/graphics/texmap/coordinated/CoordinatedBackingStoreProxy.cpp
user: login: nikolaszimmermann
body: I came to the same conclusion, it's not pretty either way. At least it's minimal invasive this way.
created_at: 2025-03-30T21:30:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,71 @@
+/*
+ * 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"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/core/SkPicture.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+#include <wtf/TZoneMalloc.h>
+#include <wtf/ThreadSafeRefCounted.h>
+
+class SkImage;
+
+namespace WebCore {
+
+class SkiaRecordingResult final : public ThreadSafeRefCounted<SkiaRecordingResult> {
+    WTF_MAKE_TZONE_ALLOCATED(SkiaRecordingResult);
+    WTF_MAKE_NONCOPYABLE(SkiaRecordingResult);
+public:
+    virtual ~SkiaRecordingResult();
+    static Ref<SkiaRecordingResult> create(sk_sp<SkPicture>&&, SkiaImageToFenceMap&&, const IntRect& recordRect, RenderingMode, bool contentsOpaque, float contentsScale);
+
+    void waitForFenceIfNeeded(const SkImage*);
+
+    bool hasFences() const { return !m_imageToFenceMap.isEmpty(); }
+    const sk_sp<SkPicture>& picture() const { return m_picture; }
+    IntRect recordRect() const { return m_recordRect; }
+    RenderingMode renderingMode() const { return m_renderingMode; }
path: Source/WebCore/platform/graphics/skia/SkiaRecordingResult.h
user: login: nikolaszimmermann
body: It has to be -- otherwise we can end up deciding between two different RenderingModes when we call determineRenderingMode() once in record() and once in replay() -- instead we should decide once on a RenderingMode, and stick with that during replay.
created_at: 2025-03-30T21:31:57Z
author_association: CONTRIBUTOR

PR comments:
user: login: webkit-early-warning-system
created_at: 2025-03-28T08:47:47Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/2b2332f113d578d866ebebe2631819b262dbbfca)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/97214 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/16837 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/7048 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/102295 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/47739 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/99259 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/17130 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/25287 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/74062 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/31259 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/100217 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/12944 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/87908 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/54407 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/12698 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/5787 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/47181 "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/5865 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/104317 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/24290 "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/83105 "Found 20 new test failures: compositing/contents-opaque/negative-z-before-html.html css3/blending/background-blend-mode-tiled-layers.html css3/filters/effect-brightness.html css3/filters/effect-contrast.html css3/filters/effect-grayscale-square.html css3/filters/effect-grayscale.html css3/filters/effect-hue-rotate-square.html css3/filters/effect-invert.html css3/filters/effect-saturate-square.html css3/filters/effect-sepia.html ... (failure)") | 
| | [  ~~🛠 vision-sim~~](https://ews-build.webkit.org/#/builders/121/builds/24665 "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/8/builds/84039 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/82519 "Passed tests") | 
| | [⏳ 🧪 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/137/builds/4741 "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/17795 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/24253 "Built successfully") | [  ~~🛠 mac-safer-cpp~~](https://ews-build.webkit.org/#/builders/120/builds/29404 "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/24076 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/27388 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/25649 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-28T10:30:12Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/3ebbb60005f3540bd214015112d824c7440e8a93)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/97220 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/16843 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/7054 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/102301 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/47745 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/99265 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/17136 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/25293 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/74067 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/31264 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/100223 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/12951 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/87918 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/54412 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/12705 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/5798 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/47187 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/82745 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/5876 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/104323 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/24296 "Built successfully") | [❌ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/17723 "Found 2 new test failures: imported/w3c/web-platform-tests/workers/abrupt-completion.html ipc/send-gpu-GetShareableBitmap-RemoteRenderingBackend.html (failure)") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/83109 "Found 15 new test failures: compositing/contents-opaque/negative-z-before-html.html css3/blending/background-blend-mode-body-image.html css3/blending/background-blend-mode-tiled-layers.html css3/filters/effect-brightness.html css3/filters/effect-contrast.html css3/filters/effect-grayscale.html css3/filters/effect-hue-rotate-square.html css3/filters/effect-invert.html css3/filters/effect-sepia.html imported/w3c/web-platform-tests/css/css-images/tiled-radial-gradients.html ... (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/24671 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/84050 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/82523 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/27111 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/4751 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/17802 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/24259 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/29410 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/24082 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/27394 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/25655 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-30T21:37:24Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/fe836c4e80b723075eb72601e532ca0bd5a1360c)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/97456 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/17081 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/7297 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/102543 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/47985 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/99501 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/17374 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/25533 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/74259 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/31440 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/100459 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/13151 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/88133 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/54604 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/12919 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/6016 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/47427 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/82950 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/6097 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/104563 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/24535 "Built successfully") | [❌ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/17892 "Found 1 new test failure: imported/w3c/web-platform-tests/workers/data-url-shared.html (failure)") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/83304 "Found 31 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-tiled-layers.html css3/filters/effect-brightness.html css3/filters/effect-contrast.html css3/filters/effect-grayscale-square.html css3/filters/effect-grayscale.html css3/filters/effect-hue-rotate-square.html css3/filters/effect-invert.html ... (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/24907 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/84260 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/82725 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/27265 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/4954 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/18102 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/24497 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/29665 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/24319 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/27633 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/25893 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: nikolaszimmermann
created_at: 2025-03-30T21:40:10Z
body: @carlosgcampos - do you know if we do run tests in HiDPI mode? Since I don't have a laptop with such a screen, could you give this a local try with Gtk+device scale? I couldn't verify if I'm taking into account the `contentsScale` correctly (eventually the simple `paintRect.location() - recordRect.location()` translation is too simple.
author_association: CONTRIBUTOR
user: login: webkit-early-warning-system
created_at: 2025-03-31T09:21:54Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/d683be13f365f0e632f8a9d1dfa66991b3e44cd0)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/97492 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/17117 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/7333 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/102580 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/48021 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/99537 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/17410 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/25569 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/74289 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/31469 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/100495 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/13184 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/88165 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/54634 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/12953 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/6047 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/47463 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/82983 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/6128 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/104599 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/24571 "Built successfully") | [  ~~🧪 mac-AS-debug-wk2~~](https://ews-build.webkit.org/#/builders/122/builds/17924 "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/1/builds/83335 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/24943 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/84291 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/82756 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/27298 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/4985 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/18162 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/24533 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/29702 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/24355 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/27669 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/25929 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-31T10:50:34Z
body: EWS run on current version of this PR (hash https://github.com/WebKit/WebKit/commit/9bef1631cb8730c71e38cbea2743d7dd25bd7fd3)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/97497 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/17122 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/7338 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/102586 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/48026 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/99542 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/17415 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/25574 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/74294 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/31474 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/100500 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/13190 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/88171 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/54639 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/12959 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/6053 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/47468 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/82988 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/6134 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/104604 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/24576 "Built successfully") | [  ~~🧪 mac-AS-debug-wk2~~](https://ews-build.webkit.org/#/builders/122/builds/17929 "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/1/builds/83340 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/24948 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/84297 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/82761 "Passed tests") | 
| [✅ 🛠 🧪 merge](https://ews-build.webkit.org/#/builders/19/builds/20819 "Built successfully and passed tests") | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/27303 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/4991 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/18167 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/24538 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/29707 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/24360 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/27674 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/25934 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-commit-queue
created_at: 2025-03-31T13:44:56Z
body: Committed 292929@main (5abe8ab78cbb): <https://commits.webkit.org/292929@main>

Reviewed commits have been landed. Closing PR #43183 and removing active labels.
author_association: COLLABORATOR
user: login: philn
created_at: 2025-03-31T17:18:12Z
body: This introduced 3 new unused-variable warnings in CoordinatedPlatformLayer.cpp
author_association: MEMBER
user: login: nikolaszimmermann
created_at: 2025-03-31T19:49:49Z
body: @philn thanks for the heads up, I will have a look
author_association: CONTRIBUTOR