[GTK][WPE] Skia Compositor: use deferred display lists to paint tiles
Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
Source/WebCore/platform/graphics/skia/SkiaUtilities.cpp
WebKit's GTK/WPE coordinated compositor tiles the page into a backing store and paints tiles concurrently using a worker thread pool. Previously each worker required a full Skia GrContext — a costly, thread-bound GPU context handle. Skia's Deferred Display List (DDL) feature decouples recording from execution: an SkDeferredDisplayListRecorder captures draw commands into a serialized list without any GPU calls, and an SkDDLPlayer replays them later on the real GrContext thread. GPU textures cannot be embedded directly into a DDL because they are bound to a specific GrContext; Skia's answer is promise images — placeholder SkImages carrying a fulfillment callback (promiseImageTexture) invoked during replay on the GPU thread to supply the actual backend texture.
This commit implements that full pipeline. Workers record into a DDL with no GL involvement, then the compositor thread replays the DDL onto the GPU surface. GPU-resident images are wrapped as Skia promise images during recording, with texture resolution deferred to replay time via PromiseImageContext callbacks. The commit adds SkiaPaintingEngine::record/replay, SkiaReplayCanvas, and PromiseImageContext.
Before (direct GL on workers): After (DDL record/replay):
Worker Thread N Worker Thread N Compositor Thread
└── GrContext (per thread) └── SkDDLRecorder └── SkiaReplayCanvas
└── paint tile → GPU surface └── record(tile) └── replay(DDL, GrContext)
└── accel image └── PromiseImageContext::
→ PromiseImage promiseImageTexture()
(placeholder) └── resolve → GPU texture
Significance
Eliminating per-worker GrContext creation removes both a major GPU resource cost and the GL-context-sharing hazards that came with cross-thread direct rendering. Tile recording on workers no longer needs any GL involvement, and a single GPU thread owns the live GrContext — a structurally simpler model. The cross-thread GPU texture ownership protocol via promise images is architecturally significant and introduces a new synchronization surface that did not previously exist in WebKit.
Audit directions
- Promise image lifetime.
PromiseImageContextwraps an accelerated image captured on a worker thread, then itspromiseImageTexture()callback is invoked on the compositor/GPU thread during replay. If the underlyingGrBackendTextureor itsGrContextis destroyed, reclaimed, or recycled between record and replay, this is a cross-thread use-after-free. The destructor~PromiseImageContextmust synchronize with any in-flight Skia GPU fulfillment callbacks — Skia only guarantees the callback is done before theSkImageis destroyed, so the refcount/lifetime management here needs careful review. - DDL characterization mismatch. DDLs are recorded against a specific
SkSurfaceCharacterization(dimensions, color type, sample count, etc.). If the replay surface's characterization diverges from the recorded one, Skia silently rejects or mishandles the DDL. An attacker-controlled sequence that changes surface properties (color space, device pixel ratio) between record and replay could trigger undefined rendering or surface state confusion. waitForRenderingCompletionAndRewrapImageIfNeeded. This method inSkiaReplayCanvasimplies that some images need re-wrapping post-replay — likely for synchronizing GPU fence completion before the image can be displayed or reused. Races in this sync path (e.g., if the fence check can be bypassed or returns spuriously) could allow a tile update to proceed before GPU work completes, leading to visual corruption or, in the worst case, accessing memory backing a completed-but-freed texture.- Atlas GPU image cross-thread access.
SkiaGPUAtlas::atlasImageForCurrentThreadwas changed — the atlas is a shared GPU texture atlas. Changes to how the atlas image is accessed per-thread in recording context vs. compositor replay deserve scrutiny for TOCTOU on the atlas handle.