Bug 1911231 - Clip fill/clear ops to viewport when possible. r=aosmond

We can avoid falling back to Skia on certain fill/clear ops so long as
the transform produces axis-aligned rectangles. In that case, we can
do a precise clip of the transformed rect to the viewport, which can
then be accelerated without any risk of fallback.

Differential Revision: https://phabricator.services.mozilla.com/D218472
This commit is contained in:
Lee Salzman 2024-08-03 18:57:01 +00:00
parent 61ef38e43f
commit def60a14fa
4 changed files with 69 additions and 37 deletions

View File

@ -1408,23 +1408,27 @@ inline ColorPattern DrawTargetWebgl::GetClearPattern() const {
DeviceColor(0.0f, 0.0f, 0.0f, IsOpaque(mFormat) ? 1.0f : 0.0f));
}
// Check if the transformed rect would contain the entire viewport.
inline bool DrawTargetWebgl::RectContainsViewport(const Rect& aRect) const {
return mTransform.PreservesAxisAlignedRectangles() &&
MatrixDouble(mTransform)
.TransformBounds(
RectDouble(aRect.x, aRect.y, aRect.width, aRect.height))
.Contains(RectDouble(GetRect()));
template <typename R>
inline RectDouble DrawTargetWebgl::TransformDouble(const R& aRect) const {
return MatrixDouble(mTransform).TransformBounds(WidenToDouble(aRect));
}
// Check if the transformed rect clips to the viewport.
inline Maybe<Rect> DrawTargetWebgl::RectClippedToViewport(
const RectDouble& aRect) const {
if (!mTransform.PreservesAxisAlignedRectangles()) {
return Nothing();
}
return Some(NarrowToFloat(aRect.SafeIntersect(RectDouble(GetRect()))));
}
// Ensure that the rect, after transform, is within reasonable precision limits
// such that when transformed and clipped in the shader it will not round bits
// from the mantissa in a way that will diverge in a noticeable way from path
// geometry calculated by the path fallback.
static inline bool RectInsidePrecisionLimits(const Rect& aRect,
const Matrix& aTransform) {
return Rect(-(1 << 20), -(1 << 20), 2 << 20, 2 << 20)
.Contains(aTransform.TransformBounds(aRect));
static inline bool RectInsidePrecisionLimits(const RectDouble& aRect) {
return RectDouble(-(1 << 20), -(1 << 20), 2 << 20, 2 << 20).Contains(aRect);
}
void DrawTargetWebgl::ClearRect(const Rect& aRect) {
@ -1433,14 +1437,16 @@ void DrawTargetWebgl::ClearRect(const Rect& aRect) {
return;
}
bool containsViewport = RectContainsViewport(aRect);
if (containsViewport) {
// If the rect encompasses the entire viewport, just clear the viewport
// instead to avoid transform issues.
DrawRect(Rect(GetRect()), GetClearPattern(),
RectDouble xformRect = TransformDouble(aRect);
bool containsViewport = false;
if (Maybe<Rect> clipped = RectClippedToViewport(xformRect)) {
// If the rect clips to viewport, just clear the clipped rect
// to avoid transform issues.
containsViewport = clipped->Size() == Size(GetSize());
DrawRect(*clipped, GetClearPattern(),
DrawOptions(1.0f, CompositionOp::OP_CLEAR), Nothing(), nullptr,
false);
} else if (RectInsidePrecisionLimits(aRect, mTransform)) {
} else if (RectInsidePrecisionLimits(xformRect)) {
// If the rect transform won't stress precision, then just use it.
DrawRect(aRect, GetClearPattern(),
DrawOptions(1.0f, CompositionOp::OP_CLEAR));
@ -2592,16 +2598,18 @@ bool SharedContextWebgl::PruneTextureMemory(size_t aMargin, bool aPruneUnused) {
void DrawTargetWebgl::FillRect(const Rect& aRect, const Pattern& aPattern,
const DrawOptions& aOptions) {
if (SupportsPattern(aPattern)) {
if (RectInsidePrecisionLimits(aRect, mTransform)) {
DrawRect(aRect, aPattern, aOptions);
return;
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 (aPattern.GetType() == PatternType::COLOR &&
RectContainsViewport(aRect)) {
// If the pattern is transform-invariant and the rect encompasses the
// entire viewport, just clip drawing to the viewport to avoid transform
// issues.
DrawRect(Rect(GetRect()), aPattern, aOptions, Nothing(), nullptr, false);
if (RectInsidePrecisionLimits(xformRect)) {
DrawRect(aRect, aPattern, aOptions);
return;
}
}
@ -2762,17 +2770,19 @@ void DrawTargetWebgl::Fill(const Path* aPath, const Pattern& aPattern,
SkRect skiaRect = SkRect::MakeEmpty();
// Draw the path as a simple rectangle with a supported pattern when possible.
if (skiaPath.isRect(&skiaRect) && SupportsPattern(aPattern)) {
Rect rect = SkRectToRect(skiaRect);
if (RectInsidePrecisionLimits(rect, mTransform)) {
DrawRect(rect, aPattern, aOptions);
return;
RectDouble rect = SkRectToRectDouble(skiaRect);
RectDouble xformRect = TransformDouble(rect);
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 (aPattern.GetType() == PatternType::COLOR &&
RectContainsViewport(rect)) {
// If the pattern is transform-invariant and the rect encompasses the
// entire viewport, just clip drawing to the viewport to avoid transform
// issues.
DrawRect(Rect(GetRect()), aPattern, aOptions, Nothing(), nullptr, false);
if (RectInsidePrecisionLimits(xformRect)) {
DrawRect(NarrowToFloat(rect), aPattern, aOptions);
return;
}
}

View File

@ -622,7 +622,10 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
ColorPattern GetClearPattern() const;
bool RectContainsViewport(const Rect& aRect) const;
template <typename R>
RectDouble TransformDouble(const R& aRect) const;
Maybe<Rect> RectClippedToViewport(const RectDouble& aRect) const;
bool ShouldAccelPath(const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions);

View File

@ -275,6 +275,13 @@ static inline Rect SkRectToRect(const SkRect& aRect) {
SkScalarToFloat(aRect.width()), SkScalarToFloat(aRect.height()));
}
static inline RectDouble SkRectToRectDouble(const SkRect& aRect) {
double x = SkScalarToDouble(aRect.x());
double y = SkScalarToDouble(aRect.y());
return RectDouble(x, y, SkScalarToDouble(aRect.right()) - x,
SkScalarToDouble(aRect.bottom()) - y);
}
static inline SkTileMode ExtendModeToTileMode(ExtendMode aMode, Axis aAxis) {
switch (aMode) {
case ExtendMode::CLAMP:

View File

@ -318,6 +318,18 @@ struct MOZ_EMPTY_BASES RectTyped
typedef RectTyped<UnknownUnits> Rect;
typedef RectTyped<UnknownUnits, double> RectDouble;
template <class Units, class D>
RectTyped<Units> NarrowToFloat(const RectTyped<Units, D>& aRect) {
return RectTyped<Units>(float(aRect.x), float(aRect.y), float(aRect.width),
float(aRect.height));
}
template <class Units, class F>
RectTyped<Units, double> WidenToDouble(const RectTyped<Units, F>& aRect) {
return RectTyped<Units, double>(double(aRect.x), double(aRect.y),
double(aRect.width), double(aRect.height));
}
template <class Units>
IntRectTyped<Units> RoundedToInt(const RectTyped<Units>& aRect) {
RectTyped<Units> copy(aRect);