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
This commit is contained in:
Lee Salzman 2024-09-05 17:24:16 +00:00
Родитель d39955ba19
Коммит 7499f0288d
4 изменённых файлов: 100 добавлений и 19 удалений

Просмотреть файл

@ -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<Rect> 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<SurfacePattern> DrawTargetWebgl::LinearGradientToSurface(
const RectDouble& aBounds, const Pattern& aPattern) {
MOZ_ASSERT(aPattern.GetType() == PatternType::LINEAR_GRADIENT);
const auto& gradient = static_cast<const LinearGradientPattern&>(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<DrawTargetSkia> 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<SourceSurface> 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<Rect> 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<SurfacePattern> 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<const PathSkia*>(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<SurfacePattern> surface =
LinearGradientToSurface(xformRect, aPattern)) {
if (DrawRect(NarrowToFloat(rect), *surface, aOptions, Nothing(),
nullptr, true, true, true)) {
return;
}
}
}
}
}

Просмотреть файл

@ -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<SurfacePattern> LinearGradientToSurface(const RectDouble& aBounds,
const Pattern& aPattern);
ColorPattern GetClearPattern() const;

Просмотреть файл

@ -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

Просмотреть файл

@ -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