Bug 1829026 - Optimize circle drawing in DrawTargetWebgl. r=aosmond

Chartjs heavily relies on circle drawing, which dispatches to the FillCircle and
StrokeCircle hooks in DrawTarget. These need to be implemented in DrawTargetWebgl.

Differential Revision: https://phabricator.services.mozilla.com/D194353
This commit is contained in:
Lee Salzman
2023-12-05 09:37:04 +00:00
parent 96de389769
commit a0e2a74c15
2 changed files with 117 additions and 26 deletions

View File

@@ -16,6 +16,7 @@
#include "mozilla/gfx/Helpers.h"
#include "mozilla/gfx/HelpersSkia.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/gfx/PathSkia.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/layers/CanvasRenderer.h"
@@ -2099,7 +2100,8 @@ bool SharedContextWebgl::DrawRectAccel(
const Rect& aRect, const Pattern& aPattern, const DrawOptions& aOptions,
Maybe<DeviceColor> aMaskColor, RefPtr<TextureHandle>* aHandle,
bool aTransformed, bool aClipped, bool aAccelOnly, bool aForceUpdate,
const StrokeOptions* aStrokeOptions, const PathVertexRange* aVertexRange) {
const StrokeOptions* aStrokeOptions, const PathVertexRange* aVertexRange,
const Matrix* aRectXform) {
// If the rect or clip rect is empty, then there is nothing to draw.
if (aRect.IsEmpty() || mClipRect.IsEmpty()) {
return true;
@@ -2121,13 +2123,19 @@ bool SharedContextWebgl::DrawRectAccel(
}
const Matrix& currentTransform = mCurrentTarget->GetTransform();
// rectXform only applies to the rect, but should not apply to the pattern,
// as it might inadvertently alter the pattern.
Matrix rectXform = currentTransform;
if (aRectXform) {
rectXform *= *aRectXform;
}
if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE && aTransformed &&
aClipped &&
(HasClipMask() || !currentTransform.PreservesAxisAlignedRectangles() ||
!currentTransform.TransformBounds(aRect).Contains(Rect(mClipAARect)) ||
(HasClipMask() || !rectXform.PreservesAxisAlignedRectangles() ||
!rectXform.TransformBounds(aRect).Contains(Rect(mClipAARect)) ||
(aPattern.GetType() == PatternType::SURFACE &&
!IsAlignedRect(aTransformed, currentTransform, aRect)))) {
!IsAlignedRect(aTransformed, rectXform, aRect)))) {
// Clear outside the mask region for masks that are not bounded by clip.
return DrawRectAccel(Rect(mClipRect), ColorPattern(DeviceColor(0, 0, 0, 0)),
DrawOptions(1.0f, CompositionOp::OP_SOURCE,
@@ -2137,8 +2145,8 @@ bool SharedContextWebgl::DrawRectAccel(
DrawOptions(aOptions.mAlpha, CompositionOp::OP_ADD,
aOptions.mAntialiasMode),
aMaskColor, aHandle, aTransformed, aClipped,
aAccelOnly, aForceUpdate, aStrokeOptions,
aVertexRange);
aAccelOnly, aForceUpdate, aStrokeOptions, aVertexRange,
aRectXform);
}
if (aOptions.mCompositionOp == CompositionOp::OP_CLEAR &&
aPattern.GetType() == PatternType::SURFACE && !aMaskColor) {
@@ -2146,7 +2154,8 @@ bool SharedContextWebgl::DrawRectAccel(
// needs to be ignored. Just use a color pattern instead.
return DrawRectAccel(aRect, ColorPattern(DeviceColor(1, 1, 1, 1)), aOptions,
Nothing(), aHandle, aTransformed, aClipped, aAccelOnly,
aForceUpdate, aStrokeOptions, aVertexRange);
aForceUpdate, aStrokeOptions, aVertexRange,
aRectXform);
}
// Set up the scissor test to reflect the clipping rectangle, if supplied.
@@ -2177,7 +2186,7 @@ bool SharedContextWebgl::DrawRectAccel(
// composition op must effectively overwrite the destination, and the
// transform must map to an axis-aligned integer rectangle.
if (Maybe<IntRect> intRect =
IsAlignedRect(aTransformed, currentTransform, aRect)) {
IsAlignedRect(aTransformed, rectXform, aRect)) {
// Only use a clear if the area is larger than a quarter or the
// viewport.
if (intRect->Area() >=
@@ -2235,7 +2244,7 @@ bool SharedContextWebgl::DrawRectAccel(
Array<float, 4> colorData = {color.b, color.g, color.r, color.a};
Matrix xform(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y);
if (aTransformed) {
xform *= currentTransform;
xform *= rectXform;
}
Array<float, 6> xformData = {xform._11, xform._12, xform._21,
xform._22, xform._31, xform._32};
@@ -2307,6 +2316,11 @@ bool SharedContextWebgl::DrawRectAccel(
if (!invMatrix.Invert()) {
break;
}
if (aRectXform) {
// If there is aRectXform, it must be applied to the source rectangle to
// generate the proper input coordinates for the inverse pattern matrix.
invMatrix.PreMultiply(*aRectXform);
}
RefPtr<WebGLTexture> tex;
IntRect bounds;
@@ -2403,7 +2417,7 @@ bool SharedContextWebgl::DrawRectAccel(
Array<float, 1> swizzleData = {format == SurfaceFormat::A8 ? 1.0f : 0.0f};
Matrix xform(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y);
if (aTransformed) {
xform *= currentTransform;
xform *= rectXform;
}
Array<float, 6> xformData = {xform._11, xform._12, xform._21,
xform._22, xform._31, xform._32};
@@ -2787,6 +2801,12 @@ void DrawTargetWebgl::Fill(const Path* aPath, const Pattern& aPattern,
DrawPath(aPath, aPattern, aOptions);
}
void DrawTargetWebgl::FillCircle(const Point& aOrigin, float aRadius,
const Pattern& aPattern,
const DrawOptions& aOptions) {
DrawCircle(aOrigin, aRadius, aPattern, aOptions);
}
QuantizedPath::QuantizedPath(const WGR::Path& aPath) : mPath(aPath) {}
QuantizedPath::QuantizedPath(QuantizedPath&& aPath) noexcept
@@ -3193,12 +3213,19 @@ already_AddRefed<TextureHandle> SharedContextWebgl::DrawStrokeMask(
bool SharedContextWebgl::DrawPathAccel(
const Path* aPath, const Pattern& aPattern, const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions, bool aAllowStrokeAlpha,
const ShadowOptions* aShadow, bool aCacheable) {
const ShadowOptions* aShadow, bool aCacheable, const Matrix* aPathXform) {
// Get the transformed bounds for the path and conservatively check if the
// bounds overlap the canvas.
const PathSkia* pathSkia = static_cast<const PathSkia*>(aPath);
const Matrix& currentTransform = mCurrentTarget->GetTransform();
Rect bounds = pathSkia->GetFastBounds(currentTransform, aStrokeOptions);
Matrix pathXform = currentTransform;
// If there is a path-specific transform that shouldn't be applied to the
// pattern, then generate a matrix that should only be used with the Skia
// path.
if (aPathXform) {
pathXform.PreMultiply(*aPathXform);
}
Rect bounds = pathSkia->GetFastBounds(pathXform, aStrokeOptions);
// If the path is empty, then there is nothing to draw.
if (bounds.IsEmpty()) {
return true;
@@ -3246,7 +3273,7 @@ bool SharedContextWebgl::DrawPathAccel(
// Use a quantized, relative (to its bounds origin) version of the path as
// a cache key to help limit cache bloat.
Maybe<QuantizedPath> qp = GenerateQuantizedPath(
mWGRPathBuilder, pathSkia->GetPath(), quantBounds, currentTransform);
mWGRPathBuilder, pathSkia->GetPath(), quantBounds, pathXform);
if (!qp) {
return false;
}
@@ -3342,13 +3369,13 @@ bool SharedContextWebgl::DrawPathAccel(
cullRect = Some(invRect);
}
SkPath fillPath;
if (pathSkia->GetFillPath(*aStrokeOptions, currentTransform, fillPath,
if (pathSkia->GetFillPath(*aStrokeOptions, pathXform, fillPath,
cullRect)) {
// printf_stderr(" stroke fill... verbs %d, points %d\n",
// int(fillPath.countVerbs()),
// int(fillPath.countPoints()));
if (Maybe<QuantizedPath> qp = GenerateQuantizedPath(
mWGRPathBuilder, fillPath, quantBounds, currentTransform)) {
mWGRPathBuilder, fillPath, quantBounds, pathXform)) {
wgrVB = GeneratePathVertexBuffer(
*qp, IntRect(-intBounds.TopLeft(), mViewportSize),
mRasterizationTruncates, outputBuffer, outputBufferCapacity);
@@ -3466,7 +3493,6 @@ bool SharedContextWebgl::DrawPathAccel(
// Ensure the the shadow is drawn at the requested offset
offset += aShadow->mOffset;
}
pathDT->SetTransform(currentTransform * Matrix::Translation(offset));
DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER,
aOptions.mAntialiasMode);
static const ColorPattern maskPattern(DeviceColor(1.0f, 1.0f, 1.0f, 1.0f));
@@ -3474,10 +3500,26 @@ bool SharedContextWebgl::DrawPathAccel(
// If the source pattern is a DrawTargetWebgl snapshot, we may shift
// targets when drawing the path, so back up the old target.
DrawTargetWebgl* oldTarget = mCurrentTarget;
if (aStrokeOptions) {
pathDT->Stroke(aPath, cachePattern, *aStrokeOptions, drawOptions);
} else {
pathDT->Fill(aPath, cachePattern, drawOptions);
{
RefPtr<const Path> path;
if (color || !aPathXform) {
// If the pattern is transform invariant or there is no pathXform, then
// it is safe to use the path directly.
path = aPath;
pathDT->SetTransform(pathXform * Matrix::Translation(offset));
} else {
// If there is a pathXform, then pre-apply that to the path to avoid
// altering the pattern.
RefPtr<PathBuilder> builder =
aPath->TransformedCopyToBuilder(*aPathXform);
path = builder->Finish();
pathDT->SetTransform(currentTransform * Matrix::Translation(offset));
}
if (aStrokeOptions) {
pathDT->Stroke(path, cachePattern, *aStrokeOptions, drawOptions);
} else {
pathDT->Fill(path, cachePattern, drawOptions);
}
}
if (aShadow && aShadow->mSigma > 0.0f) {
// Blur the shadow if required.
@@ -3543,6 +3585,34 @@ void DrawTargetWebgl::DrawPath(const Path* aPath, const Pattern& aPattern,
}
}
// DrawCircle is a more specialized version of DrawPath that attempts to cache
// a unit circle.
void DrawTargetWebgl::DrawCircle(const Point& aOrigin, float aRadius,
const Pattern& aPattern,
const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions) {
if (ShouldAccelPath(aOptions, aStrokeOptions)) {
// Cache a unit circle and transform it to avoid creating a path repeatedly.
if (!mUnitCirclePath) {
mUnitCirclePath = MakePathForCircle(*this, Point(0, 0), 1);
}
// Scale and translate the circle to the desired shape.
Matrix circleXform(aRadius, 0, 0, aRadius, aOrigin.x, aOrigin.y);
if (mSharedContext->DrawPathAccel(mUnitCirclePath, aPattern, aOptions,
aStrokeOptions, true, nullptr, true,
&circleXform)) {
return;
}
}
MarkSkiaChanged(aOptions);
if (aStrokeOptions) {
mSkia->StrokeCircle(aOrigin, aRadius, aPattern, *aStrokeOptions, aOptions);
} else {
mSkia->FillCircle(aOrigin, aRadius, aPattern, aOptions);
}
}
void DrawTargetWebgl::DrawSurface(SourceSurface* aSurface, const Rect& aDest,
const Rect& aSource,
const DrawSurfaceOptions& aSurfOptions,
@@ -3748,10 +3818,10 @@ bool DrawTargetWebgl::StrokeLineAccel(const Point& aStart, const Point& aEnd,
}
Matrix lineXform(dirX.x, dirX.y, dirY.x, dirY.y, start.x - 0.5f * dirY.x,
start.y - 0.5f * dirY.y);
AutoRestoreTransform restore(this);
ConcatTransform(lineXform);
if (DrawRect(Rect(0, 0, 1, 1), aPattern, aOptions, Nothing(), nullptr, true,
true, true)) {
if (PrepareContext() &&
mSharedContext->DrawRectAccel(Rect(0, 0, 1, 1), aPattern, aOptions,
Nothing(), nullptr, true, true, true,
false, nullptr, nullptr, &lineXform)) {
return true;
}
}
@@ -3818,6 +3888,13 @@ void DrawTargetWebgl::Stroke(const Path* aPath, const Pattern& aPattern,
DrawPath(aPath, aPattern, aOptions, &aStrokeOptions, allowStrokeAlpha);
}
void DrawTargetWebgl::StrokeCircle(const Point& aOrigin, float aRadius,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions) {
DrawCircle(aOrigin, aRadius, aPattern, aOptions, &aStrokeOptions);
}
bool DrawTargetWebgl::ShouldUseSubpixelAA(ScaledFont* aFont,
const DrawOptions& aOptions) {
AntialiasMode aaMode = aFont->GetDefaultAAMode();

View File

@@ -289,7 +289,8 @@ class SharedContextWebgl : public mozilla::RefCounted<SharedContextWebgl>,
bool aTransformed = true, bool aClipped = true,
bool aAccelOnly = false, bool aForceUpdate = false,
const StrokeOptions* aStrokeOptions = nullptr,
const PathVertexRange* aVertexRange = nullptr);
const PathVertexRange* aVertexRange = nullptr,
const Matrix* aRectXform = nullptr);
already_AddRefed<TextureHandle> DrawStrokeMask(
const PathVertexRange& aVertexRange, const IntSize& aSize);
@@ -298,7 +299,8 @@ class SharedContextWebgl : public mozilla::RefCounted<SharedContextWebgl>,
const StrokeOptions* aStrokeOptions = nullptr,
bool aAllowStrokeAlpha = false,
const ShadowOptions* aShadow = nullptr,
bool aCacheable = true);
bool aCacheable = true,
const Matrix* aPathXform = nullptr);
bool DrawGlyphsAccel(ScaledFont* aFont, const GlyphBuffer& aBuffer,
const Pattern& aPattern, const DrawOptions& aOptions,
@@ -380,6 +382,9 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
RefPtr<TextureHandle> mSnapshotTexture;
// Cached unit circle path
RefPtr<Path> mUnitCirclePath;
// Store a log of clips currently pushed so that they can be used to init
// the clip state of temporary DTs.
struct ClipStack {
@@ -497,6 +502,12 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
const DrawOptions& aOptions = DrawOptions()) override;
void Fill(const Path* aPath, const Pattern& aPattern,
const DrawOptions& aOptions = DrawOptions()) override;
void FillCircle(const Point& aOrigin, float aRadius, const Pattern& aPattern,
const DrawOptions& aOptions = DrawOptions()) override;
void StrokeCircle(const Point& aOrigin, float aRadius,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions = StrokeOptions(),
const DrawOptions& aOptions = DrawOptions()) override;
void SetPermitSubpixelAA(bool aPermitSubpixelAA) override;
void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
@@ -601,6 +612,9 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions = nullptr,
bool aAllowStrokeAlpha = false);
void DrawCircle(const Point& aOrigin, float aRadius, const Pattern& aPattern,
const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions = nullptr);
bool MarkChanged();