From 378e9808bf09080a566b57a0c97305edc4c905c8 Mon Sep 17 00:00:00 2001 From: Tim Nguyen Date: Thu, 20 Feb 2020 22:07:32 +0000 Subject: [PATCH] Bug 1615862 - Handle conic-gradients in nsCSSGradientRenderer for WebRender. r=emilio,mstange Differential Revision: https://phabricator.services.mozilla.com/D63018 --HG-- extra : moz-landing-system : lando --- layout/painting/nsCSSRenderingBorders.cpp | 29 +++-- layout/painting/nsCSSRenderingGradients.cpp | 111 ++++++++++++++++---- layout/painting/nsCSSRenderingGradients.h | 13 ++- layout/style/ServoStyleConstsInlines.h | 8 +- layout/style/nsStyleStruct.cpp | 20 +++- 5 files changed, 145 insertions(+), 36 deletions(-) diff --git a/layout/painting/nsCSSRenderingBorders.cpp b/layout/painting/nsCSSRenderingBorders.cpp index 4cef49606185..112ff05c9a3d 100644 --- a/layout/painting/nsCSSRenderingBorders.cpp +++ b/layout/painting/nsCSSRenderingBorders.cpp @@ -3663,18 +3663,21 @@ ImgDrawResult nsCSSBorderImageRenderer::CreateWebRenderCommands( bool noVerticalBorders = widths[0] <= epsilon && widths[2] < epsilon; bool noHorizontalBorders = widths[1] <= epsilon && widths[3] < epsilon; - // Border image with no border. It's a little silly but WebRender currently does - // not handle this. We could fall back to a blob image but there are reftests that - // are sensible to the test going through a blob while the reference doesn't. + // Border image with no border. It's a little silly but WebRender + // currently does not handle this. We could fall back to a blob image + // but there are reftests that are sensible to the test going through a + // blob while the reference doesn't. if (noVerticalBorders && noHorizontalBorders) { - aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), rendering, key.value()); + aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), rendering, + key.value()); break; } // Fall-back if we want to fill the middle area and opposite edges are // both empty. // TODO(bug 1609893): moving some of the repetition handling code out - // of the image shader will make it easier to handle these cases properly. + // of the image shader will make it easier to handle these cases + // properly. if (noHorizontalBorders || noVerticalBorders) { return ImgDrawResult::NOT_SUPPORTED; } @@ -3704,8 +3707,11 @@ ImgDrawResult nsCSSBorderImageRenderer::CreateWebRenderCommands( LayoutDevicePoint lineStart; LayoutDevicePoint lineEnd; LayoutDeviceSize gradientRadius; + LayoutDevicePoint gradientCenter; + float gradientAngle; renderer.BuildWebRenderParameters(1.0, extendMode, stops, lineStart, - lineEnd, gradientRadius); + lineEnd, gradientRadius, gradientCenter, + gradientAngle); if (gradient.IsLinear()) { LayoutDevicePoint startPoint = @@ -3723,7 +3729,7 @@ ImgDrawResult nsCSSBorderImageRenderer::CreateWebRenderCommands( extendMode, wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2], outset[3])); - } else { + } else if (gradient.IsRadial()) { aBuilder.PushBorderRadialGradient( dest, clip, !aItem->BackfaceIsHidden(), wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]), @@ -3731,6 +3737,15 @@ ImgDrawResult nsCSSBorderImageRenderer::CreateWebRenderCommands( wr::ToLayoutSize(gradientRadius), stops, extendMode, wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2], outset[3])); + } else { + MOZ_ASSERT(gradient.IsConic()); + aBuilder.PushBorderConicGradient( + dest, clip, !aItem->BackfaceIsHidden(), + wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]), + mFill, wr::ToLayoutPoint(gradientCenter), gradientAngle, stops, + extendMode, + wr::ToLayoutSideOffsets(outset[0], outset[1], outset[2], + outset[3])); } break; } diff --git a/layout/painting/nsCSSRenderingGradients.cpp b/layout/painting/nsCSSRenderingGradients.cpp index 8b289706b6c7..a13834b502b8 100644 --- a/layout/painting/nsCSSRenderingGradients.cpp +++ b/layout/painting/nsCSSRenderingGradients.cpp @@ -225,6 +225,17 @@ static Tuple ComputeRadialGradientLine( return MakeTuple(start, end, radiusX, radiusY); } +// Compute the center and the start angle of the conic gradient. +static Tuple ComputeConicGradientProperties( + const StyleGradient& aGradient, const CSSSize& aBoxSize) { + const auto& conic = aGradient.AsConic(); + const Position& position = conic.position; + float angle = static_cast(conic.angle.ToRadians()); + CSSPoint center = ResolvePosition(position, aBoxSize); + + return MakeTuple(center, angle); +} + static float Interpolate(float aF1, float aF2, float aFrac) { return aF1 + aFrac * (aF2 - aF1); } @@ -563,22 +574,40 @@ static Maybe GetSpecifiedGradientPosition( return Some(pos.ResolveToCSSPixels(aLineLength) / aLineLength); } -static nsTArray ComputeColorStops(ComputedStyle* aComputedStyle, - const StyleGradient& aGradient, - CSSCoord aLineLength) { - auto items = aGradient.IsLinear() ? aGradient.AsLinear().items.AsSpan() - : aGradient.AsRadial().items.AsSpan(); +// aLineLength argument is unused for conic-gradients. +static Maybe GetSpecifiedGradientPosition( + const StyleGenericGradientItem& aItem, + CSSCoord aLineLength) { + if (aItem.IsSimpleColorStop()) { + return Nothing(); + } - MOZ_ASSERT(items.Length() >= 2, + const StyleAngleOrPercentage& pos = aItem.IsComplexColorStop() + ? aItem.AsComplexColorStop().position + : aItem.AsInterpolationHint(); + + if (pos.IsPercentage()) { + return Some(pos.AsPercentage()._0); + } + + return Some(pos.AsAngle().ToRadians() / (2 * M_PI)); +} + +template +static nsTArray ComputeColorStopsForItems( + ComputedStyle* aComputedStyle, + Span> aItems, + CSSCoord aLineLength) { + MOZ_ASSERT(aItems.Length() >= 2, "The parser should reject gradients with less than two stops"); - nsTArray stops(items.Length()); + nsTArray stops(aItems.Length()); // If there is a run of stops before stop i that did not have specified // positions, then this is the index of the first stop in that run. Maybe firstUnsetPosition; - for (size_t i = 0; i < items.Length(); ++i) { - const auto& stop = items[i]; + for (size_t i = 0; i < aItems.Length(); ++i) { + const auto& stop = aItems[i]; double position; Maybe specifiedPosition = @@ -589,7 +618,7 @@ static nsTArray ComputeColorStops(ComputedStyle* aComputedStyle, } else if (i == 0) { // First stop defaults to position 0.0 position = 0.0; - } else if (i == items.Length() - 1) { + } else if (i == aItems.Length() - 1) { // Last stop defaults to position 1.0 position = 1.0; } else { @@ -634,6 +663,21 @@ static nsTArray ComputeColorStops(ComputedStyle* aComputedStyle, return stops; } +static nsTArray ComputeColorStops(ComputedStyle* aComputedStyle, + const StyleGradient& aGradient, + CSSCoord aLineLength) { + if (aGradient.IsLinear()) { + return ComputeColorStopsForItems( + aComputedStyle, aGradient.AsLinear().items.AsSpan(), aLineLength); + } + if (aGradient.IsRadial()) { + return ComputeColorStopsForItems( + aComputedStyle, aGradient.AsRadial().items.AsSpan(), aLineLength); + } + return ComputeColorStopsForItems( + aComputedStyle, aGradient.AsConic().items.AsSpan(), aLineLength); +} + nsCSSGradientRenderer nsCSSGradientRenderer::Create( nsPresContext* aPresContext, ComputedStyle* aComputedStyle, const StyleGradient& aGradient, const nsSize& aIntrinsicSize) { @@ -641,19 +685,26 @@ nsCSSGradientRenderer nsCSSGradientRenderer::Create( // Compute "gradient line" start and end relative to the intrinsic size of // the gradient. - CSSPoint lineStart, lineEnd; - CSSCoord radiusX = 0, radiusY = 0; // for radial gradients only + CSSPoint lineStart, lineEnd, center; // center is for conic gradients only + CSSCoord radiusX = 0, radiusY = 0; // for radial gradients only + float angle = 0.0; // for conic gradients only if (aGradient.IsLinear()) { Tie(lineStart, lineEnd) = ComputeLinearGradientLine(aPresContext, aGradient, srcSize); - } else { + } else if (aGradient.IsRadial()) { Tie(lineStart, lineEnd, radiusX, radiusY) = ComputeRadialGradientLine(aGradient, srcSize); + } else { + MOZ_ASSERT(aGradient.IsConic()); + Tie(center, angle) = ComputeConicGradientProperties(aGradient, srcSize); } // Avoid sending Infs or Nans to downwind draw targets. if (!lineStart.IsFinite() || !lineEnd.IsFinite()) { lineStart = lineEnd = CSSPoint(0, 0); } + if (!center.IsFinite()) { + center = CSSPoint(0, 0); + } CSSCoord lineLength = NS_hypot(lineEnd.x - lineStart.x, lineEnd.y - lineStart.y); @@ -677,6 +728,11 @@ nsCSSGradientRenderer nsCSSGradientRenderer::Create( }; renderer.mRadiusX = aPresContext->CSSPixelsToDevPixels(radiusX); renderer.mRadiusY = aPresContext->CSSPixelsToDevPixels(radiusY); + renderer.mCenter = { + aPresContext->CSSPixelsToDevPixels(center.x), + aPresContext->CSSPixelsToDevPixels(center.y), + }; + renderer.mAngle = angle; return renderer; } @@ -869,8 +925,7 @@ void nsCSSGradientRenderer::Paint(gfxContext& aContext, const nsRect& aDest, gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y, gradientEnd.x, gradientEnd.y); - } else { - MOZ_ASSERT(mGradient->IsRadial()); + } else if (mGradient->IsRadial()) { NS_ASSERTION(firstStop >= 0.0, "Negative stops not allowed for radial gradients"); @@ -895,6 +950,9 @@ void nsCSSGradientRenderer::Paint(gfxContext& aContext, const nsRect& aDest, matrix.PreScale(1.0, mRadiusX / mRadiusY); matrix.PreTranslate(-mLineStart); } + } else { + // conic-gradient is only implemented for WebRender + return; } // Use a pattern transform to take account of source and dest rects matrix.PreTranslate(gfxPoint(mPresContext->CSSPixelsToDevPixels(aSrc.x), @@ -1120,7 +1178,8 @@ bool nsCSSGradientRenderer::TryPaintTilesWithExtendMode( void nsCSSGradientRenderer::BuildWebRenderParameters( float aOpacity, wr::ExtendMode& aMode, nsTArray& aStops, LayoutDevicePoint& aLineStart, LayoutDevicePoint& aLineEnd, - LayoutDeviceSize& aGradientRadius) { + LayoutDeviceSize& aGradientRadius, LayoutDevicePoint& aGradientCenter, + float& aGradientAngle) { aMode = mGradient->Repeating() ? wr::ExtendMode::Repeat : wr::ExtendMode::Clamp; @@ -1136,6 +1195,8 @@ void nsCSSGradientRenderer::BuildWebRenderParameters( aLineStart = LayoutDevicePoint(mLineStart.x, mLineStart.y); aLineEnd = LayoutDevicePoint(mLineEnd.x, mLineEnd.y); aGradientRadius = LayoutDeviceSize(mRadiusX, mRadiusY); + aGradientCenter = LayoutDevicePoint(mCenter.x, mCenter.y); + aGradientAngle = mAngle; } void nsCSSGradientRenderer::BuildWebRenderDisplayItems( @@ -1151,8 +1212,10 @@ void nsCSSGradientRenderer::BuildWebRenderDisplayItems( LayoutDevicePoint lineStart; LayoutDevicePoint lineEnd; LayoutDeviceSize gradientRadius; + LayoutDevicePoint gradientCenter; + float gradientAngle; BuildWebRenderParameters(aOpacity, extendMode, stops, lineStart, lineEnd, - gradientRadius); + gradientRadius, gradientCenter, gradientAngle); nscoord appUnitsPerDevPixel = mPresContext->AppUnitsPerDevPixel(); @@ -1188,6 +1251,9 @@ void nsCSSGradientRenderer::BuildWebRenderDisplayItems( lineStart.x = (lineStart.x - srcTransform.x) * srcTransform.width; lineStart.y = (lineStart.y - srcTransform.y) * srcTransform.height; + gradientCenter.x = (gradientCenter.x - srcTransform.x) * srcTransform.width; + gradientCenter.y = (gradientCenter.y - srcTransform.y) * srcTransform.height; + if (mGradient->IsLinear()) { lineEnd.x = (lineEnd.x - srcTransform.x) * srcTransform.width; lineEnd.y = (lineEnd.y - srcTransform.y) * srcTransform.height; @@ -1199,8 +1265,7 @@ void nsCSSGradientRenderer::BuildWebRenderDisplayItems( mozilla::wr::ToLayoutPoint(lineEnd), stops, extendMode, mozilla::wr::ToLayoutSize(firstTileBounds.Size()), mozilla::wr::ToLayoutSize(tileSpacing)); - } else { - MOZ_ASSERT(mGradient->IsRadial()); + } else if (mGradient->IsRadial()) { gradientRadius.width *= srcTransform.width; gradientRadius.height *= srcTransform.height; @@ -1211,6 +1276,14 @@ void nsCSSGradientRenderer::BuildWebRenderDisplayItems( mozilla::wr::ToLayoutSize(gradientRadius), stops, extendMode, mozilla::wr::ToLayoutSize(firstTileBounds.Size()), mozilla::wr::ToLayoutSize(tileSpacing)); + } else { + MOZ_ASSERT(mGradient->IsConic()); + aBuilder.PushConicGradient( + mozilla::wr::ToLayoutRect(gradientBounds), + mozilla::wr::ToLayoutRect(clipBounds), aIsBackfaceVisible, + mozilla::wr::ToLayoutPoint(gradientCenter), gradientAngle, stops, + extendMode, mozilla::wr::ToLayoutSize(firstTileBounds.Size()), + mozilla::wr::ToLayoutSize(tileSpacing)); } } diff --git a/layout/painting/nsCSSRenderingGradients.h b/layout/painting/nsCSSRenderingGradients.h index 5452ed3a95f2..de1896b843da 100644 --- a/layout/painting/nsCSSRenderingGradients.h +++ b/layout/painting/nsCSSRenderingGradients.h @@ -65,7 +65,9 @@ class nsCSSGradientRenderer final { nsTArray& aStops, LayoutDevicePoint& aLineStart, LayoutDevicePoint& aLineEnd, - LayoutDeviceSize& aGradientRadius); + LayoutDeviceSize& aGradientRadius, + LayoutDevicePoint& aGradientCenter, + float& aGradientAngle); /** * Build display items for the gradient @@ -89,7 +91,8 @@ class nsCSSGradientRenderer final { : mPresContext(nullptr), mGradient(nullptr), mRadiusX(0.0), - mRadiusY(0.0) {} + mRadiusY(0.0), + mAngle(0.0) {} /** * Attempts to paint the tiles for a gradient by painting it once to an @@ -107,8 +110,10 @@ class nsCSSGradientRenderer final { nsPresContext* mPresContext; const StyleGradient* mGradient; nsTArray mStops; - gfxPoint mLineStart, mLineEnd; - double mRadiusX, mRadiusY; + gfxPoint mLineStart, mLineEnd; // only for linear/radial gradients + double mRadiusX, mRadiusY; // only for radial gradients + gfxPoint mCenter; // only for conic gradients + float mAngle; // only for conic gradients }; } // namespace mozilla diff --git a/layout/style/ServoStyleConstsInlines.h b/layout/style/ServoStyleConstsInlines.h index 10cf5983dd6d..c2a78899b546 100644 --- a/layout/style/ServoStyleConstsInlines.h +++ b/layout/style/ServoStyleConstsInlines.h @@ -441,7 +441,13 @@ inline imgRequestProxy* StyleComputedImageUrl::GetImage() const { template <> inline bool StyleGradient::Repeating() const { - return IsLinear() ? AsLinear().repeating : AsRadial().repeating; + if (IsLinear()) { + return AsLinear().repeating; + } + if (IsRadial()) { + return AsRadial().repeating; + } + return AsConic().repeating; } template <> diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index dce8f3adefca..ebf44d67116e 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1402,11 +1402,10 @@ nsChangeHint nsStyleTableBorder::CalcDifference( } } -template <> -bool StyleGradient::IsOpaque() const { - auto items = - IsLinear() ? AsLinear().items.AsSpan() : AsRadial().items.AsSpan(); - for (auto& stop : items) { +template +static bool GradientItemsAreOpaque( + Span> aItems) { + for (auto& stop : aItems) { if (stop.IsInterpolationHint()) { continue; } @@ -1423,6 +1422,17 @@ bool StyleGradient::IsOpaque() const { return true; } +template <> +bool StyleGradient::IsOpaque() const { + if (IsLinear()) { + return GradientItemsAreOpaque(AsLinear().items.AsSpan()); + } + if (IsRadial()) { + return GradientItemsAreOpaque(AsRadial().items.AsSpan()); + } + return GradientItemsAreOpaque(AsConic().items.AsSpan()); +} + // -------------------- // CachedBorderImageData