Add support for rendering simple SVG items with WebRender instead of blob images
Categories
(Core :: Graphics: WebRender, enhancement, P3)
Tracking
()
| 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.
| Assignee | ||
Updated•2 months ago
|
| Assignee | ||
Comment 1•2 months ago
|
||
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.
| Assignee | ||
Comment 2•2 months ago
|
||
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.
| Assignee | ||
Comment 3•2 months ago
|
||
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.
| Assignee | ||
Comment 4•2 months ago
|
||
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.
| Assignee | ||
Comment 5•2 months ago
|
||
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).
| Assignee | ||
Comment 6•2 months ago
|
||
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.
| Assignee | ||
Comment 7•2 months ago
|
||
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.
| Assignee | ||
Comment 8•2 months ago
|
||
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.
| Assignee | ||
Comment 9•2 months ago
|
||
| Assignee | ||
Comment 10•2 months ago
|
||
Comment 11•2 months ago
|
||
| Assignee | ||
Updated•2 months ago
|
Comment 12•1 month ago
|
||
| bugherder | ||
| Assignee | ||
Comment 13•1 month ago
|
||
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.
| Assignee | ||
Comment 14•1 month ago
|
||
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.
| Assignee | ||
Comment 15•1 month ago
|
||
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.
| Assignee | ||
Comment 16•1 month ago
|
||
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.
| Assignee | ||
Comment 17•1 month ago
|
||
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.
Comment 18•1 month ago
|
||
| Assignee | ||
Comment 19•1 month ago
|
||
Landing all of the changes preffed off, I will flip the pref ins followup bugs to avoid having this one span over multiple releases.
| Assignee | ||
Updated•1 month ago
|
Comment 20•1 month ago
|
||
| bugherder | ||
https://hg.mozilla.org/mozilla-central/rev/5584a388f057
https://hg.mozilla.org/mozilla-central/rev/93c4cb60487c
https://hg.mozilla.org/mozilla-central/rev/6678440ed0c5
https://hg.mozilla.org/mozilla-central/rev/382dd10cafd3
https://hg.mozilla.org/mozilla-central/rev/ed82320ec287
https://hg.mozilla.org/mozilla-central/rev/b47a4b82c9c6
https://hg.mozilla.org/mozilla-central/rev/8f527e2681db
https://hg.mozilla.org/mozilla-central/rev/dc8af46e61ac
https://hg.mozilla.org/mozilla-central/rev/ea71b9242f98
https://hg.mozilla.org/mozilla-central/rev/a869a320fbad
https://hg.mozilla.org/mozilla-central/rev/3891d4911350
https://hg.mozilla.org/mozilla-central/rev/db50d271f52d
https://hg.mozilla.org/mozilla-central/rev/ea2da4373b39
https://hg.mozilla.org/mozilla-central/rev/bfc619a49941
https://hg.mozilla.org/mozilla-central/rev/b7afb0e60a50
Updated•18 days ago
|
Description
•