From 7499f0288da84f55e8dfe2959335499dcbb982cd Mon Sep 17 00:00:00 2001 From: Lee Salzman Date: Thu, 5 Sep 2024 17:24:16 +0000 Subject: [PATCH] Bug 1916907 - Support linear gradients in DrawTargetWebgl. r=aosmond This attempts to map linear gradient to 1D textures of sufficient resolution for a given rendered primitive. The 1D texture can then be rendered using the image shader, without having to add any specialized gradient shaders. The 1D ramp textures are much smaller than uploading a texture for an entire fallback primitive, which becomes a significant performance benefit in the case of primitives that take up a large area on screen. In the future it might be possible to cache these ramp textures, but for now they remain uncached. Differential Revision: https://phabricator.services.mozilla.com/D221107 --- dom/canvas/DrawTargetWebgl.cpp | 111 ++++++++++++++++++--- dom/canvas/DrawTargetWebgl.h | 2 + layout/reftests/canvas/reftest.list | 4 +- layout/reftests/css-gradients/reftest.list | 2 +- 4 files changed, 100 insertions(+), 19 deletions(-) diff --git a/dom/canvas/DrawTargetWebgl.cpp b/dom/canvas/DrawTargetWebgl.cpp index 3835ec2535a5..322446171603 100644 --- a/dom/canvas/DrawTargetWebgl.cpp +++ b/dom/canvas/DrawTargetWebgl.cpp @@ -2598,24 +2598,91 @@ bool SharedContextWebgl::PruneTextureMemory(size_t aMargin, bool aPruneUnused) { return mNumTextureHandles < oldItems; } -void DrawTargetWebgl::FillRect(const Rect& aRect, const Pattern& aPattern, - const DrawOptions& aOptions) { - if (SupportsPattern(aPattern)) { - RectDouble xformRect = TransformDouble(aRect); - if (aPattern.GetType() == PatternType::COLOR) { - if (Maybe clipped = RectClippedToViewport(xformRect)) { - // If the pattern is transform-invariant and the rect clips to the - // viewport, just clip drawing to the viewport to avoid transform - // issues. - DrawRect(*clipped, aPattern, aOptions, Nothing(), nullptr, false); - return; +// Attempt to convert a linear gradient to a 1D ramp texture. +Maybe DrawTargetWebgl::LinearGradientToSurface( + const RectDouble& aBounds, const Pattern& aPattern) { + MOZ_ASSERT(aPattern.GetType() == PatternType::LINEAR_GRADIENT); + const auto& gradient = static_cast(aPattern); + // The gradient points must be transformed by the gradient's matrix. + Point gradBegin = gradient.mMatrix.TransformPoint(gradient.mBegin); + Point gradEnd = gradient.mMatrix.TransformPoint(gradient.mEnd); + // Get the gradient points in user-space. + Point begin = mTransform.TransformPoint(gradBegin); + Point end = mTransform.TransformPoint(gradEnd); + // Find the normalized direction of the gradient and its length. + Point dir = end - begin; + float len = dir.Length(); + dir = dir / len; + // Restrict the rendered bounds to fall within the canvas. + Rect visBounds = NarrowToFloat(aBounds.SafeIntersect(RectDouble(GetRect()))); + // Calculate the distances along the gradient direction of the bounds. + float dist0 = (visBounds.TopLeft() - begin).DotProduct(dir); + float distX = visBounds.width * dir.x; + float distY = visBounds.height * dir.y; + float minDist = floorf( + std::max(dist0 + std::min(distX, 0.0f) + std::min(distY, 0.0f), 0.0f)); + float maxDist = ceilf( + std::min(dist0 + std::max(distX, 0.0f) + std::max(distY, 0.0f), len)); + // Calculate the approximate size of the ramp texture, and see if it would be + // sufficiently smaller than just rendering the primitive. + float subLen = maxDist - minDist; + if (subLen > 0 && subLen < 0.5f * visBounds.Area()) { + // Create a 1D texture to contain the gradient ramp. Reserve two extra + // texels at the beginning and end of the ramp to account for clamping. + RefPtr dt = new DrawTargetSkia; + if (dt->Init(IntSize(int32_t(subLen + 2), 1), SurfaceFormat::B8G8R8A8)) { + // Fill the section of the gradient ramp that is actually used. + dt->FillRect(Rect(dt->GetRect()), + LinearGradientPattern(Point(1 - minDist, 0.0f), + Point(len + 1 - minDist, 0.0f), + gradient.mStops)); + if (RefPtr snapshot = dt->Snapshot()) { + // Calculate a matrix that will map the gradient ramp texture onto the + // actual direction of the gradient. + Point gradDir = (gradEnd - gradBegin) / len; + Point tangent = Point(-gradDir.y, gradDir.x) / gradDir.Length(); + SurfacePattern surfacePattern( + snapshot, ExtendMode::CLAMP, + Matrix(gradDir.x, gradDir.y, tangent.x, tangent.y, gradBegin.x, + gradBegin.y) + .PreTranslate(minDist - 1, 0)); + if (SupportsPattern(surfacePattern)) { + return Some(surfacePattern); + } } } - if (RectInsidePrecisionLimits(xformRect)) { - DrawRect(aRect, aPattern, aOptions); + } + return Nothing(); +} + +void DrawTargetWebgl::FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions) { + RectDouble xformRect = TransformDouble(aRect); + if (aPattern.GetType() == PatternType::COLOR) { + if (Maybe clipped = RectClippedToViewport(xformRect)) { + // If the pattern is transform-invariant and the rect clips to the + // viewport, just clip drawing to the viewport to avoid transform + // issues. + DrawRect(*clipped, aPattern, aOptions, Nothing(), nullptr, false); return; } } + if (RectInsidePrecisionLimits(xformRect)) { + if (SupportsPattern(aPattern)) { + DrawRect(aRect, aPattern, aOptions); + return; + } + if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) { + if (Maybe surface = + LinearGradientToSurface(xformRect, aPattern)) { + if (DrawRect(aRect, *surface, aOptions, Nothing(), nullptr, true, true, + true)) { + return; + } + } + } + } + if (!mWebglValid) { MarkSkiaChanged(aOptions); mSkia->FillRect(aRect, aPattern, aOptions); @@ -2772,7 +2839,7 @@ void DrawTargetWebgl::Fill(const Path* aPath, const Pattern& aPattern, const SkPath& skiaPath = static_cast(aPath)->GetPath(); SkRect skiaRect = SkRect::MakeEmpty(); // Draw the path as a simple rectangle with a supported pattern when possible. - if (skiaPath.isRect(&skiaRect) && SupportsPattern(aPattern)) { + if (skiaPath.isRect(&skiaRect)) { RectDouble rect = SkRectToRectDouble(skiaRect); RectDouble xformRect = TransformDouble(rect); if (aPattern.GetType() == PatternType::COLOR) { @@ -2784,9 +2851,21 @@ void DrawTargetWebgl::Fill(const Path* aPath, const Pattern& aPattern, return; } } + if (RectInsidePrecisionLimits(xformRect)) { - DrawRect(NarrowToFloat(rect), aPattern, aOptions); - return; + if (SupportsPattern(aPattern)) { + DrawRect(NarrowToFloat(rect), aPattern, aOptions); + return; + } + if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) { + if (Maybe surface = + LinearGradientToSurface(xformRect, aPattern)) { + if (DrawRect(NarrowToFloat(rect), *surface, aOptions, Nothing(), + nullptr, true, true, true)) { + return; + } + } + } } } diff --git a/dom/canvas/DrawTargetWebgl.h b/dom/canvas/DrawTargetWebgl.h index 6c0e1d4543d1..98a7904967c8 100644 --- a/dom/canvas/DrawTargetWebgl.h +++ b/dom/canvas/DrawTargetWebgl.h @@ -619,6 +619,8 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr { bool aTransformed = true, bool aClipped = true, bool aAccelOnly = false, bool aForceUpdate = false, const StrokeOptions* aStrokeOptions = nullptr); + Maybe LinearGradientToSurface(const RectDouble& aBounds, + const Pattern& aPattern); ColorPattern GetClearPattern() const; diff --git a/layout/reftests/canvas/reftest.list b/layout/reftests/canvas/reftest.list index d013124315fc..343cc7dfa3df 100644 --- a/layout/reftests/canvas/reftest.list +++ b/layout/reftests/canvas/reftest.list @@ -103,8 +103,8 @@ fuzzy(0-1,0-43) == 1201272-1.html 1201272-1-ref.html == 1238795-1.html 1238795-1-ref.html == 1303534-1.html 1303534-1-ref.html -fuzzy-if(cocoaWidget,0-1,0-1410) fuzzy-if(winWidget,0-1,0-1410) == 1304353-text-global-alpha-1.html 1304353-text-global-alpha-1-ref.html -fuzzy(0-1,0-1410) == 1304353-text-global-alpha-2.html 1304353-text-global-alpha-2-ref.html +fuzzy-if(cocoaWidget,0-1,0-1420) fuzzy-if(winWidget,0-1,0-1420) == 1304353-text-global-alpha-1.html 1304353-text-global-alpha-1-ref.html +fuzzy(0-1,0-1420) == 1304353-text-global-alpha-2.html 1304353-text-global-alpha-2-ref.html fuzzy-if(winWidget,0-94,0-1575) fuzzy-if(cocoaWidget,0-1,0-34) == 1304353-text-global-composite-op-1.html 1304353-text-global-composite-op-1-ref.html == text-indent-1a.html text-indent-1-ref.html diff --git a/layout/reftests/css-gradients/reftest.list b/layout/reftests/css-gradients/reftest.list index 0803f7ec3574..268cc1f2e8f1 100644 --- a/layout/reftests/css-gradients/reftest.list +++ b/layout/reftests/css-gradients/reftest.list @@ -1,5 +1,5 @@ fuzzy(0-4,0-89700) == linear-1a.html linear-1-ref.html -fuzzy(0-2,0-23918) == linear-keywords-1a.html linear-keywords-1-ref.html +fuzzy(0-2,0-27600) == linear-keywords-1a.html linear-keywords-1-ref.html == linear-diagonal-1a.html linear-diagonal-1-ref.html == linear-diagonal-2a.html linear-diagonal-2-ref.html == linear-diagonal-3a.html linear-diagonal-3-ref.html