Closed Bug 2070993 Opened 2 months ago Closed 1 month ago

Add support for rendering simple SVG items with WebRender instead of blob images

Categories

(Core :: Graphics: WebRender, enhancement, P3)

enhancement

Tracking

()

RESOLVED FIXED
160 Branch
Tracking Status
firefox160 --- fixed

People

(Reporter: nical, Assigned: nical)

References

(Blocks 1 open bug)

Details

Attachments

(15 external files)

WebRender is capable of rendering more SVG items than we currently let it. This is because WebRenderCommandBuilder only attempts to make a few select cases active WebRender items. This bug tracks adding plumbing for rendering more SVG items with WebRender.

Depends on: 2070998
No longer depends on: 2070998
Summary: [meta] Render simple SVG items with WebRender instead of blob images → Render simple SVG items with WebRender instead of blob images
Depends on: 2070998

Each change is behind its own pref, off by default:

  • gfx.webrender.svg-images.opacity: <image> opacity is passed as the
    WebRender image color multiplier instead of forcing a blob.
  • gfx.webrender.svg-images.non-uniform-scale: images do not need the
    uniform scale that shapes need for WebRender anti-aliasing, so they can
    be active under any scale.
  • gfx.webrender.svg-shapes.blend: shapes and images with mix-blend-mode no
    longer bail. The blend is performed by the enclosing nsDisplayBlendMode
    stacking context, which is what the blob path relies on as well.
  • gfx.webrender.svg-shapes.crisp-edges: shape-rendering: crispEdges and
    optimizeSpeed turn off anti-aliasing on the emitted rect.

Add SVGGeometryElement::SimpleShape, a description of the geometry that maps
onto WebRender primitives: axis aligned rectangles with elliptical corner
radii (which covers <rect> with rx/ry, <circle> and <ellipse>) and axis
aligned lines. It is separate from SimplePath because the Skia painting path
relies on SimplePath only describing shapes for which DrawTarget has a
dedicated fill/stroke method.

SVGGeometryFrame::CreateWebRenderCommands uses it and emits a WebRender
rounded rect (a thick border, which WebRender caches as an image) for the
non rectangular cases. Rounded shapes with shape-rendering: crispEdges stay
in the blob since WebRender always anti-aliases rounded corners.

DisplayListBuilder::PushRoundedRect gains an overload taking explicit corner
radii; the existing overload (an inscribed ellipse) is expressed in terms of
it.

Gated behind gfx.webrender.svg-shapes.ellipses, off by default. With the
pref on, the following reftests differ from their references by anti-aliasing
only (max difference up to 67 on a handful of pixels, all on shape edges)
and will need fuzz when the pref is enabled: clipPath-and-shape-rendering-01,
path-01, mask-opacity-02..05, symbol-01, marker-viewBox-01,
moz-only/zoomed-svg-with-viewBox-01, svg-integration/clip-path-transform-001.

A new layout/reftests/svg/webrender directory compares each file rendered
with the pref on against the same file rendered with the pref off.

A solid, undashed stroke of width w on a rect, rounded rect, circle or
ellipse is a WebRender border of width w around the shape inflated by w/2.
The outer corner radius is the shape's radius plus w/2; for sharp rect
corners it follows stroke-linejoin (square for miter, w/2 for round). Axis
aligned <line>s are a rect of thickness w, extended by w/2 for square and
round caps, the latter drawn as a rounded rect.

paint-order decides whether the fill or the stroke is pushed first.
fill-opacity and stroke-opacity are folded into the colors; the element
opacity is folded in only when CanOptimizeOpacity says so, otherwise it is
applied by the enclosing nsDisplayOpacity item as before.

Kept in the blob: dashed strokes, bevel joins, miter limits below sqrt(2),
non-scaling strokes, non color stroke paint, non axis aligned lines,
strokes with shape-rendering: crispEdges (WebRender borders are always
anti-aliased), and shapes with markers.

Rounded shapes and strokes are also kept in the blob when the inherited
transform rotates or skews, since WebRender renders them from axis aligned
cached images whose edges come out blurry under such transforms. For the
same reason IsItemProbablyActive no longer lets shapes that merely could be
active pull a rotated or skewed 2D transform out of the blob: the blob would
then be rasterized unrotated and resampled instead of having the transform
baked in. The transform's own scale is folded into the uniform scale check
that its children see.

Gated behind gfx.webrender.svg-shapes.strokes, off by default. With the pref
on, non-scaling-stroke-01 differs from its reference: the reference's plain
stroked rect is now drawn by WebRender with correct half-pixel edges while
the test's non-scaling stroke path renders them crisp. That test needs fuzz
or a different reference when the pref is enabled.

The save state only recorded the length of the display item buffer, so a
reference frame, scroll frame or sticky frame defined between save() and
restore() kept its spatial tree entry while its id was handed out again to
the next spatial node, which trips the duplicate id assertion in the scene
builder. Record the spatial tree buffer length and truncate it too.

SVGTextFrame::CreateWebRenderCommands walks the rendered runs the same way
PaintSVG does. For each run it pushes a WebRender reference frame carrying
the run transform (glyph position, 1 / mFontSizeScaleFactor, rotation from
rotate="" or a textPath, the textLength scale) and paints the nsTextFrame at
the origin through a TextDrawTarget, so glyph positions reach WebRender
untransformed and WebRender applies the scale and rotation when rasterizing.

Runs that cannot be expressed this way make the whole item stay in the blob:
anything ShouldRenderAsPath accepts (strokes, paint servers, translucent
fills), text-shadow, text-rendering: geometricPrecision, selection, the
caret, opacity on intermediate <tspan> ancestors (the blob path applies it
by grouping), and font sizes over WebRender's glyph rasterization limit.
Features that only the text painting code can detect (unserializable fonts,
-webkit-text-stroke, SVG and COLR glyphs) are reported by the TextDrawTarget
and handled by recording everything under a DisplayListBuilder save point.

The blob grouping code asks DisplaySVGText::ShouldBeActive, which is a dry
run of the same code with the commands discarded afterwards. Active text
therefore pays for two text painting passes; caching the result per paint is
left as a follow up once the approach has proven itself.

DisplaySVGText moves to the header so that WebRenderCommandBuilder can call
it. TextDrawTarget gains SetBounds so that one instance can serve several
runs with different bounds.

Gated behind gfx.webrender.svg-text, off by default. With the pref on,
layout/reftests/svg/text/text-shadow and dynamic-text-shadow differ from
their reference by one color unit on 25 pixels (the reference text is now
drawn by WebRender while the shadowed test text stays in the blob).

ShouldRenderAsPath sends text with fill-opacity below 1 through the path
based painter so that overlapping glyphs blend once. WebRender text applies
the color per glyph, which is the same behavior HTML text with a translucent
color has, so with gfx.webrender.svg-text.fill-opacity on such text is
accepted by the WebRender path as long as it has no stroke and no shadow.

Text decorations already work with the WebRender path since the normal
nsTextFrame decoration code handles the TextDrawTarget; the new reftest
covers them together with the translucent fills.

SVGGradientFrame::GetWebRenderGradient resolves the stops (following href
chains, applying stop-opacity and the graphic opacity, interpolating in
linearRGB when color-interpolation asks for it) and maps the gradient
geometry to the user space of the painted frame through the same
objectBoundingBox and gradientTransform matrix that the Skia path uses.

A linear gradient is mapped by transforming its two end points, which is
only valid when the lines of constant color stay perpendicular to the
vector; other transforms make the gradient fall back to the blob. A radial
gradient becomes a WebRender radial gradient with elliptical radii under an
axis aligned scale, or a circular one under a similarity; focal points and
skews fall back. spreadMethod="reflect" is expressed as a repeating
gradient over twice the vector with mirrored stops. Degenerate gradients
(no stops, one stop, zero length vector) become a solid fill like they do
in GetPaintServerPattern.

SVGGeometryFrame::CreateWebRenderCommands uses this for fills whose paint
server is a gradient. Rounded shapes clip the gradient with a rounded rect
clip. Gradient strokes stay in the blob.

Gated behind gfx.webrender.svg-shapes.gradients, off by default. With the
pref on, paint-order-02 differs from its reference by anti-aliasing only.

SVGClipPathFrame::GetSimpleClipShape recognizes a trivial <clipPath> whose
single child is a rect, rounded rect, circle or ellipse and whose combined
child transform, clipPath transform and clipPathUnits mapping is an axis
aligned scale and translation. DetermineMaskUsage then reports such clips as
simple clip shapes and CreateSimpleClipRegion turns them into a WebRender
rect or rounded rect clip instead of rasterizing an A8 mask image. The
shape is mapped from the clipped frame's user space to the reference frame
the same way SVGIntegrationUtils positions mask painting.

Inside SVG blob groups, IsItemProbablyActive treats a mask item with such a
clip as free and lets its children decide the activity, instead of keeping
the subtree in the blob. This exercises CreateSimpleClipRegion for CSS basic
shapes on SVG frames, which added the item's ToReferenceFrame() offset to a
reference box that is already in the frame's user space; the offset now
excludes the frame's own position for SVG leaf frames.

Two prefs, both off by default: gfx.webrender.svg-simple-clips for the clip
conversion and gfx.webrender.svg-mask-propagates-activity for the activity
change, since the latter changes layerization on its own.

With the clip pref on, the svg-integration clip-path-circle-* and
clip-path-ellipse-* tests pass exactly because their references use
url(#clipPath) and now render the same way as the CSS shapes; their
fuzzy-if(!useDrawSnapshot,...) annotations need a lower bound of 0 when the
pref is enabled.

Keywords: leave-open

SVGContextPaint::GetDrawMode sets GLYPH_STROKE for any stroke paint other
than "none", including a stroke whose width or whose opacity is zero and so
paints nothing. ShouldRenderAsPath treats those two cases as not needing the
path rendering route, so they reach the WebRender text path and trip its
assertion that no stroke gets that far. zero-stroke-01.svg, which strokes
text at stroke-width="0", crashes debug builds with the WebRender SVG text
pref on.

nsTextFrame strokes glyphs only for -webkit-text-stroke, and the text drawer
rejects that separately, so the SVG stroke is simply not painted here. That
matches what the blob image path does with a zero width stroke, which also
goes through plain PaintText. Drop the assertion and record the invariant it
was trying to state in a comment.

lengthAdjust="spacingAndGlyphs" scales the glyphs along the inline axis only,
so the run transform that TextRenderedRun hands out is a non-uniform scale.
Sideways vertical glyphs reach WebRender already transposed, through the
TRANSPOSE and FLIP glyph flags that gfxFont sets for a text drawer, and
WebRender folds the run transform in after that transpose. The scale
therefore lands on the glyph's cross axis: the glyphs come out squashed
across the baseline instead of along it, and the run overflows the box the
test covers.

Reject the combination so those runs stay in the blob image, where the
rotation is applied to the draw target instead and the order works out.
Horizontal text is unaffected: with no transpose, a non-uniform run scale
composes correctly.

Fixes lengthAdjust-vertical.svg and lengthAdjust-large-font-vertical.svg with
the WebRender SVG text pref on.

PushRect takes a flag that sets PrimitiveFlags::ANTIALISED, which tells
WebRender to resolve the primitive's edges in the shader and to snap nothing

  • neither the primitive rect nor its clips. The stroke borders and the
    gradient fills that SVG shapes push had no such flag, so a stroke or a
    gradient whose edges fall between device pixels was moved onto the grid while
    the plain rect fill beside it was not. The stroked rect in
    non-scaling-stroke-01-ref.svg came out crisp where the same geometry drawn by
    the blob image is anti-aliased, and a gradient rendered under a rotation
    (where WebRender has no snapping transform and so snaps nothing) disagreed
    with itself under the identity transform.

Plumb the flag through wr_dp_push_border, wr_dp_push_linear_gradient and
wr_dp_push_radial_gradient, and set it on the stroke border and the gradient
fills in SVGGeometryFrame, where it follows ShouldAntiAlias as the rect fill
already does. Every other caller keeps the previous behaviour.

Rounded shape fills deliberately stay as they were. Their curve is
anti-aliased by the border shader regardless, and the device pixel snapping
is what makes their cached corner textures composite exactly: taking it away
resampled the corners and moved circle and rounded rect edges by a fraction
of a pixel, which showed up in mask-opacity-02 to -05,
rect-with-rx-and-ry-01, patterned-svg-under-transformed-html-01, the
filter-marked-line family and the as-image tests.

An anti-aliased border also opts out of the border thickness snapping in
NormalBorderData::update. That snapping gives a CSS border an even
whole-pixel thickness on each side (bug 1258112, bug 1950029), but an SVG
stroke width is geometry that has to stay exact: rounding a 3.33 device pixel
stroke to 3 shifts its inner edge by a third of a pixel. The corner radius
snapping still applies, since it is about sampling the cached corner texture
rather than about where the border's edges are.

Each of these compares geometry that WebRender now draws with primitives
against the same geometry drawn by the blob image, or against a downscaled
rasterization of it. What is left after the anti-aliasing fixes in this
series is sub-pixel: edges resolved by the shader instead of by Skia, and
gradient interpolation that rounds differently, which shows up as a large
count of pixels differing by one or two.

non-scaling-stroke-01 the reference's stroke is a WebRender border
downscale-svg-1a/b/c/e/f the reference's stroked rect is anti-aliased
where the downscaled image is resampled
corner-split the reference's stroked rounded rect
621918-2 rotated glyphs from WebRender text
feComponentTransfer-discrete filter input rounding
feComposite-operator-lighter same, and no longer Windows only
box-decoration-break-01 same fuzz as box-decoration-break-03
clip-path-polygon-013 a native clip edge at a half pixel
svg-origin-length-cm/pt-00* gradient interpolation plus one corner pixel
blending-002 stroke edges over a blended fill
marker-context-fill-transform a marker's gradient against the path's

The values are ranges that hold with the prefs on and off, so they do not
depend on the pref flip landing.

Axis-aligned linear gradients that are split into two-stop segment quads
dropped the primitive's aligned AA edges, so the outer edges of an
ANTIALISED gradient were rounded to the pixel grid. This path runs for
gradients under 256 pixels only when dithering is on, which made SVG
gradient fills differ between hardware and software WebRender.

Pushed by nsilva@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/b2abeeeea1fd https://hg.mozilla.org/integration/autoland/rev/5584a388f057 Fuzz the reftests whose references differ by anti-aliasing with the WebRender SVG item prefs on. r=gfx-reviewers,lsalzman https://github.com/mozilla-firefox/firefox/commit/1f8142cb3be3 https://hg.mozilla.org/integration/autoland/rev/93c4cb60487c Relax a few bail-outs in the SVG shape and image WebRender paths. r=emilio https://github.com/mozilla-firefox/firefox/commit/b17c823a651f https://hg.mozilla.org/integration/autoland/rev/6678440ed0c5 Draw filled circles, ellipses and rounded rects in SVG with WebRender. r=layout-reviewers,emilio https://github.com/mozilla-firefox/firefox/commit/8c4717d29ea5 https://hg.mozilla.org/integration/autoland/rev/382dd10cafd3 Draw solid strokes on simple SVG shapes with WebRender. r=gfx-reviewers,lsalzman https://github.com/mozilla-firefox/firefox/commit/fb7eb87dc5c5 https://hg.mozilla.org/integration/autoland/rev/ed82320ec287 Roll back spatial tree items in DisplayListBuilder::restore. r=gfx-reviewers,lsalzman https://github.com/mozilla-firefox/firefox/commit/b3a2850c51ca https://hg.mozilla.org/integration/autoland/rev/b47a4b82c9c6 Draw SVG text with a plain opaque fill using WebRender text primitives. r=gfx-reviewers,layout-reviewers,jfkthame,lsalzman https://github.com/mozilla-firefox/firefox/commit/04053be1ffe7 https://hg.mozilla.org/integration/autoland/rev/8f527e2681db Draw SVG text with a translucent fill using WebRender text primitives. r=gfx-reviewers,lsalzman https://github.com/mozilla-firefox/firefox/commit/a9fcfe1d00e4 https://hg.mozilla.org/integration/autoland/rev/dc8af46e61ac Draw gradient fills on simple SVG shapes with WebRender gradients. r=layout-reviewers,dshin https://github.com/mozilla-firefox/firefox/commit/000ff6cef5e0 https://hg.mozilla.org/integration/autoland/rev/ea71b9242f98 Express trivial clip-path: url() references as native WebRender clips. r=layout-reviewers,emilio https://github.com/mozilla-firefox/firefox/commit/a32a72da55c1 https://hg.mozilla.org/integration/autoland/rev/a869a320fbad Add a mostly-active heuristic. r=gfx-reviewers,lsalzman https://github.com/mozilla-firefox/firefox/commit/be18b4a3c1ad https://hg.mozilla.org/integration/autoland/rev/3891d4911350 Don't assert on an SVG text stroke that paints nothing in the WebRender text path. r=firefox-svg-reviewers,longsonr https://github.com/mozilla-firefox/firefox/commit/2e537a78fd1f https://hg.mozilla.org/integration/autoland/rev/db50d271f52d Keep vertical text with a lengthAdjust scale in the blob image. r=firefox-svg-reviewers,longsonr https://github.com/mozilla-firefox/firefox/commit/0e20ebb64e2b https://hg.mozilla.org/integration/autoland/rev/ea2da4373b39 Anti-alias the WebRender borders and gradients that SVG shapes push. r=gfx-reviewers,gw https://github.com/mozilla-firefox/firefox/commit/867b94c52d37 https://hg.mozilla.org/integration/autoland/rev/bfc619a49941 Keep the anti-aliased edges of decomposed linear gradients. r=gfx-reviewers,lsalzman https://github.com/mozilla-firefox/firefox/commit/f6d1a646d167 https://hg.mozilla.org/integration/autoland/rev/b7afb0e60a50 apply code formatting via Lando

Landing all of the changes preffed off, I will flip the pref ins followup bugs to avoid having this one span over multiple releases.

Summary: Render simple SVG items with WebRender instead of blob images → Add support for rendering simple SVG items with WebRender instead of blob images
Keywords: leave-open
Regressions: 2081167
Regressions: 2082871
QA Whiteboard: [qa-triage-done-c161/b160]
Regressions: 2087575
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: