Skip to content

Commit 84a16ff

Browse files
REGRESSION(305273@main): [GTK] Another PDF rendering regression
https://bugs.webkit.org/show_bug.cgi?id=306621 Reviewed by Carlos Garcia Campos. The canvas 2D operation recording feature (webkit.org/b/303575) introduced a mechanism to batch canvas operations into an SkPicture for deferred GPU replay. When flushing the recording, the implementation switched between a recording canvas and the GPU surface canvas. To preserve drawing state across these switches, it attempted to copy individual GraphicsContext properties (fill color, stroke, CTM, etc.) from one context to the other. This approach was inherently fragile: it could not reproduce the full save/restore nesting, clip stack, or transparency layer state, causing PDF rendering and other complex painting scenarios to break. Replace the state-copying approach with a state replay mechanism. GraphicsContextSkia now optionally tracks the full sequence of save/restore operations, transparency layers, and clip operations (rect, path, shader) along with their associated transforms. When the canvas recording is flushed and the switchable canvas is redirected to a new target, replayStateOnCanvas() reconstructs the exact save/clip/CTM nesting on that canvas, ensuring drawing state is fully preserved. Unify SkiaState and LayerState into a single SkiaState struct that handles both regular saves and transparency layers. Move recording context creation from lazy initialization into the constructor, eliminating the need for ensureCanvasRecordingContext() and the error-prone copyGraphicsState() helper. Test: fast/canvas/canvas-recording-clip-state-across-frames.html * LayoutTests/fast/canvas/canvas-recording-clip-state-across-frames-expected.html: Added. * LayoutTests/fast/canvas/canvas-recording-clip-state-across-frames.html: Added. * Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp: (WebCore::GraphicsContextSkia::pushSkiaState): (WebCore::GraphicsContextSkia::popSkiaState): (WebCore::GraphicsContextSkia::recordClipIfNeeded): (WebCore::GraphicsContextSkia::save): (WebCore::GraphicsContextSkia::restore): (WebCore::GraphicsContextSkia::drawOutsetShadow): (WebCore::GraphicsContextSkia::createFillPaint const): (WebCore::GraphicsContextSkia::createStrokePaint const): (WebCore::GraphicsContextSkia::clip): (WebCore::GraphicsContextSkia::clipPath): (WebCore::GraphicsContextSkia::clipToImageBuffer): (WebCore::GraphicsContextSkia::saveLayer): (WebCore::GraphicsContextSkia::restoreLayer): (WebCore::GraphicsContextSkia::setLineCap): (WebCore::GraphicsContextSkia::setLineDash): (WebCore::GraphicsContextSkia::setLineJoin): (WebCore::GraphicsContextSkia::setMiterLimit): (WebCore::GraphicsContextSkia::clipOut): (WebCore::GraphicsContextSkia::enableStateReplayTracking): (WebCore::GraphicsContextSkia::replayStateOnCanvas const): * Source/WebCore/platform/graphics/skia/GraphicsContextSkia.h: * Source/WebCore/platform/graphics/skia/ImageBufferSkiaAcceleratedBackend.cpp: (WebCore::ImageBufferSkiaAcceleratedBackend::~ImageBufferSkiaAcceleratedBackend): (WebCore::ImageBufferSkiaAcceleratedBackend::context): (WebCore::ImageBufferSkiaAcceleratedBackend::ensureCanvasRecordingContext): (WebCore::ImageBufferSkiaAcceleratedBackend::flushCanvasRecordingContextIfNeeded): (WebCore::ImageBufferSkiaAcceleratedBackend::flushContext): (WebCore::ImageBufferSkiaAcceleratedBackend::prepareForDisplay): (WebCore::ImageBufferSkiaAcceleratedBackend::copyGraphicsState): Deleted. * Source/WebCore/platform/graphics/skia/ImageBufferSkiaAcceleratedBackend.h: Canonical link: https://commits.webkit.org/308033@main
1 parent 1de0bb4 commit 84a16ff

File tree

6 files changed

+293
-102
lines changed

6 files changed

+293
-102
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<style>
2+
canvas {
3+
width: 200px;
4+
height: 200px;
5+
}
6+
</style>
7+
<body>
8+
<p>This test verifies that clip state is preserved across canvas recording flush boundaries.</p>
9+
<canvas id="canvas"></canvas>
10+
<script>
11+
const canvas = document.getElementById('canvas');
12+
const ctx = canvas.getContext('2d');
13+
canvas.width = 200;
14+
canvas.height = 200;
15+
16+
// Green background.
17+
ctx.fillStyle = 'green';
18+
ctx.fillRect(0, 0, 200, 200);
19+
20+
// Blue square only in the clipped area.
21+
ctx.fillStyle = 'blue';
22+
ctx.fillRect(50, 50, 100, 100);
23+
</script>
24+
</body>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<style>
2+
canvas {
3+
width: 200px;
4+
height: 200px;
5+
}
6+
</style>
7+
<body>
8+
<p>This test verifies that clip state is preserved across canvas recording flush boundaries.</p>
9+
<canvas id="canvas"></canvas>
10+
<script>
11+
if (window.testRunner)
12+
testRunner.waitUntilDone();
13+
14+
const canvas = document.getElementById('canvas');
15+
const ctx = canvas.getContext('2d');
16+
canvas.width = 200;
17+
canvas.height = 200;
18+
19+
// Fill entire canvas green.
20+
ctx.fillStyle = 'green';
21+
ctx.fillRect(0, 0, 200, 200);
22+
23+
// Set up a clipping region.
24+
ctx.save();
25+
ctx.beginPath();
26+
ctx.rect(50, 50, 100, 100);
27+
ctx.clip();
28+
29+
// Use a double requestAnimationFrame to guarantee at least one
30+
// composite cycle (prepareForDisplay) happens between setting up
31+
// the clip and the subsequent draw. This flushes the canvas
32+
// recording to the GPU surface and starts a new recording.
33+
requestAnimationFrame(() => {
34+
requestAnimationFrame(() => {
35+
// This fillRect should still be clipped to (50,50)-(150,150).
36+
// Without proper state replay the clip would be lost after
37+
// the recording flush, and the blue would cover the entire canvas.
38+
ctx.fillStyle = 'blue';
39+
ctx.fillRect(0, 0, 200, 200);
40+
ctx.restore();
41+
42+
if (window.testRunner)
43+
testRunner.notifyDone();
44+
});
45+
});
46+
</script>
47+
</body>

0 commit comments

Comments
 (0)