Bug 1799402 - Use WGR to rasterize paths in DrawTargetWebgl. r=jrmuizel

This adds a path vertex buffer where triangle list output from WGR is stored.
Each PathCacheEntry can potentially reference a range of vertexes in this buffer
corresponding to triangles for that entry. When this buffer is full, it gets
orphaned and clears corresponding cache entries, so that it can start anew.

Differential Revision: https://phabricator.services.mozilla.com/D161479
This commit is contained in:
Lee Salzman 2022-11-12 05:42:30 +00:00
parent 7a5b0b36e4
commit bea9f37e42
8 changed files with 549 additions and 133 deletions

View File

@ -3349,6 +3349,16 @@ void ClientWebGLContext::RawBufferData(GLenum target, const uint8_t* srcBytes,
////
void ClientWebGLContext::RawBufferSubData(GLenum target,
WebGLsizeiptr dstByteOffset,
const uint8_t* srcBytes,
size_t srcLen) {
const FuncScope funcScope(*this, "bufferSubData");
Run<RPROC(BufferSubData)>(target, dstByteOffset,
RawBuffer<>({srcBytes, srcLen}));
}
void ClientWebGLContext::BufferSubData(GLenum target,
WebGLsizeiptr dstByteOffset,
const dom::ArrayBuffer& src) {

View File

@ -833,7 +833,6 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
FuncScope(FuncScope&&) = delete;
};
protected:
// The scope of the function at the top of the current WebGL function call
// stack
@ -1425,8 +1424,11 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
void BufferData(GLenum target, const dom::ArrayBufferView& srcData,
GLenum usage, GLuint srcElemOffset = 0,
GLuint srcElemCountOverride = 0);
void RawBufferData(GLenum target, const uint8_t* srcBytes, size_t srcLen,
GLenum usage);
void RawBufferSubData(GLenum target, WebGLsizeiptr dstByteOffset,
const uint8_t* srcBytes, size_t srcLen);
void BufferSubData(GLenum target, WebGLsizeiptr dstByteOffset,
const dom::ArrayBufferView& src, GLuint srcElemOffset = 0,

View File

@ -427,6 +427,8 @@ bool DrawTargetWebgl::SharedContext::Initialize() {
mMaxTextureSize = mWebgl->Limits().maxTex2dSize;
CachePrefs();
if (!CreateShaders()) {
// There was a non-recoverable error when trying to init shaders.
sContextInitError = true;
@ -1001,22 +1003,37 @@ void DrawTargetWebgl::ReleaseBits(uint8_t* aData) {
}
}
// Format is x, y, alpha
static const float kRectVertexData[12] = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f};
// Orphans the contents of the path vertex buffer. The beginning of the buffer
// always contains data for a simple rectangle draw to avoid needing to switch
// buffers.
void DrawTargetWebgl::SharedContext::ResetPathVertexBuffer() {
mWebgl->BindBuffer(LOCAL_GL_ARRAY_BUFFER, mPathVertexBuffer.get());
mWebgl->RawBufferData(
LOCAL_GL_ARRAY_BUFFER, nullptr,
std::max(size_t(mPathVertexCapacity), sizeof(kRectVertexData)),
LOCAL_GL_DYNAMIC_DRAW);
mWebgl->RawBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0,
(const uint8_t*)kRectVertexData,
sizeof(kRectVertexData));
mPathVertexOffset = sizeof(kRectVertexData);
}
// Attempts to create all shaders and resources to be used for drawing commands.
// Returns whether or not this succeeded.
bool DrawTargetWebgl::SharedContext::CreateShaders() {
if (!mVertexArray) {
mVertexArray = mWebgl->CreateVertexArray();
if (!mPathVertexArray) {
mPathVertexArray = mWebgl->CreateVertexArray();
}
if (!mVertexBuffer) {
mVertexBuffer = mWebgl->CreateBuffer();
static const float rectData[8] = {0.0f, 0.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f, 1.0f};
mWebgl->BindVertexArray(mVertexArray.get());
mWebgl->BindBuffer(LOCAL_GL_ARRAY_BUFFER, mVertexBuffer.get());
mWebgl->RawBufferData(LOCAL_GL_ARRAY_BUFFER, (const uint8_t*)rectData,
sizeof(rectData), LOCAL_GL_STATIC_DRAW);
if (!mPathVertexBuffer) {
mPathVertexBuffer = mWebgl->CreateBuffer();
mWebgl->BindVertexArray(mPathVertexArray.get());
ResetPathVertexBuffer();
mWebgl->EnableVertexAttribArray(0);
mWebgl->VertexAttribPointer(0, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, 0);
mWebgl->VertexAttribPointer(0, 3, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, 0);
}
if (!mSolidProgram) {
// AA is computed by using the basis vectors of the transform to determine
@ -1027,24 +1044,27 @@ bool DrawTargetWebgl::SharedContext::CreateShaders() {
// minimum coverage is then chosen by the fragment shader to use as an AA
// coverage value to modulate the color.
auto vsSource =
u"attribute vec2 a_vertex;\n"
u"attribute vec3 a_vertex;\n"
"uniform vec2 u_transform[3];\n"
"uniform vec2 u_viewport;\n"
"uniform float u_aa;\n"
"varying vec4 v_dist;\n"
"varying vec2 v_cliptc;\n"
"varying vec4 v_dist;\n"
"varying float v_alpha;\n"
"void main() {\n"
" vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
" dot(u_transform[1], u_transform[1]));\n"
" vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
" scale *= invScale;\n"
" vec2 extrude = a_vertex + invScale * (2.0 * a_vertex - 1.0);\n"
" vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
"1.0);\n"
" vec2 vertex = u_transform[0] * extrude.x +\n"
" u_transform[1] * extrude.y +\n"
" u_transform[2];\n"
" gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
" v_cliptc = vertex / u_viewport;\n"
" v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
" v_alpha = a_vertex.z;\n"
"}\n"_ns;
auto fsSource =
u"precision mediump float;\n"
@ -1052,10 +1072,11 @@ bool DrawTargetWebgl::SharedContext::CreateShaders() {
"uniform sampler2D u_clipmask;\n"
"varying vec2 v_cliptc;\n"
"varying vec4 v_dist;\n"
"varying float v_alpha;\n"
"void main() {\n"
" float clip = texture2D(u_clipmask, v_cliptc).r;\n"
" vec2 dist = min(v_dist.xy, v_dist.zw);\n"
" float aa = clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
" float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
" gl_FragColor = clip * aa * u_color;\n"
"}\n"_ns;
RefPtr<WebGLShaderJS> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER);
@ -1099,7 +1120,7 @@ bool DrawTargetWebgl::SharedContext::CreateShaders() {
if (!mImageProgram) {
auto vsSource =
u"attribute vec2 a_vertex;\n"
u"attribute vec3 a_vertex;\n"
"uniform vec2 u_viewport;\n"
"uniform float u_aa;\n"
"uniform vec2 u_transform[3];\n"
@ -1107,12 +1128,14 @@ bool DrawTargetWebgl::SharedContext::CreateShaders() {
"varying vec2 v_cliptc;\n"
"varying vec2 v_texcoord;\n"
"varying vec4 v_dist;\n"
"varying float v_alpha;\n"
"void main() {\n"
" vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
" dot(u_transform[1], u_transform[1]));\n"
" vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
" scale *= invScale;\n"
" vec2 extrude = a_vertex + invScale * (2.0 * a_vertex - 1.0);\n"
" vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
"1.0);\n"
" vec2 vertex = u_transform[0] * extrude.x +\n"
" u_transform[1] * extrude.y +\n"
" u_transform[2];\n"
@ -1122,6 +1145,7 @@ bool DrawTargetWebgl::SharedContext::CreateShaders() {
" u_texmatrix[1] * extrude.y +\n"
" u_texmatrix[2];\n"
" v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
" v_alpha = a_vertex.z;\n"
"}\n"_ns;
auto fsSource =
u"precision mediump float;\n"
@ -1133,12 +1157,13 @@ bool DrawTargetWebgl::SharedContext::CreateShaders() {
"varying vec2 v_cliptc;\n"
"varying vec2 v_texcoord;\n"
"varying vec4 v_dist;\n"
"varying float v_alpha;\n"
"void main() {\n"
" vec2 tc = clamp(v_texcoord, u_texbounds.xy, u_texbounds.zw);\n"
" vec4 image = texture2D(u_sampler, tc);\n"
" float clip = texture2D(u_clipmask, v_cliptc).r;\n"
" vec2 dist = min(v_dist.xy, v_dist.zw);\n"
" float aa = clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
" float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
" gl_FragColor = clip * aa * u_color *\n"
" mix(image, image.rrrr, u_swizzle);\n"
"}\n"_ns;
@ -1601,7 +1626,7 @@ bool DrawTargetWebgl::SharedContext::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 StrokeOptions* aStrokeOptions, const PathVertexRange* aVertexRange) {
// If the rect or clip rect is empty, then there is nothing to draw.
if (aRect.IsEmpty() || mClipRect.IsEmpty()) {
return true;
@ -1615,6 +1640,7 @@ bool DrawTargetWebgl::SharedContext::DrawRectAccel(
// If only accelerated drawing was requested, bail out without software
// drawing fallback.
if (!aAccelOnly) {
MOZ_ASSERT(!aVertexRange);
mCurrentTarget->DrawRectFallback(aRect, aPattern, aOptions, aMaskColor,
aTransformed, aClipped, aStrokeOptions);
}
@ -1641,7 +1667,7 @@ bool DrawTargetWebgl::SharedContext::DrawRectAccel(
DrawOptions(1.0f, CompositionOp::OP_SOURCE,
aOptions.mAntialiasMode),
Nothing(), nullptr, aTransformed, aClipped, aAccelOnly,
aForceUpdate, aStrokeOptions)) {
aForceUpdate, aStrokeOptions, aVertexRange)) {
return false;
}
}
@ -1649,7 +1675,7 @@ bool DrawTargetWebgl::SharedContext::DrawRectAccel(
aRect, aPattern,
DrawOptions(aOptions.mAlpha, op, aOptions.mAntialiasMode), aMaskColor,
aHandle, aTransformed, aClipped, aAccelOnly, aForceUpdate,
aStrokeOptions);
aStrokeOptions, aVertexRange);
}
// Set up the scissor test to reflect the clipping rectangle, if supplied.
@ -1666,13 +1692,16 @@ bool DrawTargetWebgl::SharedContext::DrawRectAccel(
// Now try to actually draw the pattern...
switch (aPattern.GetType()) {
case PatternType::COLOR: {
mCurrentTarget->mProfile.OnUncachedDraw();
if (!aVertexRange) {
// Only an uncached draw if not using the vertex cache.
mCurrentTarget->mProfile.OnUncachedDraw();
}
auto color = static_cast<const ColorPattern&>(aPattern).mColor;
float a = color.a * aOptions.mAlpha;
DeviceColor premulColor(color.r * a, color.g * a, color.b * a, a);
if (((a == 1.0f && aOptions.mCompositionOp == CompositionOp::OP_OVER) ||
aOptions.mCompositionOp == CompositionOp::OP_SOURCE) &&
!aStrokeOptions && !HasClipMask()) {
!aStrokeOptions && !aVertexRange && !HasClipMask()) {
// Certain color patterns can be mapped to scissored clears. The
// composition op must effectively overwrite the destination, and the
// transform must map to an axis-aligned integer rectangle.
@ -1719,12 +1748,13 @@ bool DrawTargetWebgl::SharedContext::DrawRectAccel(
{(const uint8_t*)viewportData, sizeof(viewportData)});
mDirtyViewport = false;
}
if (mDirtyAA || aStrokeOptions) {
// Native lines use line smoothing.
float aaData = aStrokeOptions ? 0.0f : 1.0f;
if (mDirtyAA || aStrokeOptions || aVertexRange) {
// Native lines use line smoothing. Generated paths provide their own
// AA as vertex alpha.
float aaData = aStrokeOptions || aVertexRange ? 0.0f : 1.0f;
mWebgl->UniformData(LOCAL_GL_FLOAT, mSolidProgramAA, false,
{(const uint8_t*)&aaData, sizeof(aaData)});
mDirtyAA = !!aStrokeOptions;
mDirtyAA = aaData == 0.0f;
}
float colorData[4] = {premulColor.b, premulColor.g, premulColor.r,
premulColor.a};
@ -1739,8 +1769,16 @@ bool DrawTargetWebgl::SharedContext::DrawRectAccel(
mWebgl->UniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramColor, false,
{(const uint8_t*)colorData, sizeof(colorData)});
// Finally draw the colored rectangle.
mWebgl->DrawArrays(
aStrokeOptions ? LOCAL_GL_LINE_LOOP : LOCAL_GL_TRIANGLE_FAN, 0, 4);
if (aVertexRange) {
// If there's a vertex range, then we need to draw triangles within from
// generated from a path stored in the path vertex buffer.
mWebgl->DrawArrays(LOCAL_GL_TRIANGLES, GLint(aVertexRange->mOffset),
GLsizei(aVertexRange->mLength));
} else {
// Otherwise we're drawing a simple stroked/filled rectangle.
mWebgl->DrawArrays(
aStrokeOptions ? LOCAL_GL_LINE_LOOP : LOCAL_GL_TRIANGLE_FAN, 0, 4);
}
success = true;
break;
}
@ -1924,15 +1962,17 @@ bool DrawTargetWebgl::SharedContext::DrawRectAccel(
{(const uint8_t*)viewportData, sizeof(viewportData)});
mDirtyViewport = false;
}
if (mDirtyAA || aStrokeOptions) {
if (mDirtyAA || aStrokeOptions || aVertexRange) {
// AA is not supported for OP_SOURCE. Native lines use line smoothing.
float aaData =
mLastCompositionOp == CompositionOp::OP_SOURCE || aStrokeOptions
? 0.0f
: 1.0f;
// Generated paths provide their own AA as vertex alpha.
float aaData = mLastCompositionOp == CompositionOp::OP_SOURCE ||
aStrokeOptions || aVertexRange
? 0.0f
: 1.0f;
mWebgl->UniformData(LOCAL_GL_FLOAT, mImageProgramAA, false,
{(const uint8_t*)&aaData, sizeof(aaData)});
mDirtyAA = !!aStrokeOptions;
mDirtyAA = aaData == 0.0f;
}
DeviceColor color = aMaskColor && format != SurfaceFormat::A8
? DeviceColor::Mask(1.0f, aMaskColor->a)
@ -2020,8 +2060,16 @@ bool DrawTargetWebgl::SharedContext::DrawRectAccel(
}
// Finally draw the image rectangle.
mWebgl->DrawArrays(
aStrokeOptions ? LOCAL_GL_LINE_LOOP : LOCAL_GL_TRIANGLE_FAN, 0, 4);
if (aVertexRange) {
// If there's a vertex range, then we need to draw triangles within from
// generated from a path stored in the path vertex buffer.
mWebgl->DrawArrays(LOCAL_GL_TRIANGLES, GLint(aVertexRange->mOffset),
GLsizei(aVertexRange->mLength));
} else {
// Otherwise we're drawing a simple stroked/filled rectangle.
mWebgl->DrawArrays(
aStrokeOptions ? LOCAL_GL_LINE_LOOP : LOCAL_GL_TRIANGLE_FAN, 0, 4);
}
// Restore the default linear filter if overridden.
if (UseNearestFilter(surfacePattern)) {
@ -2160,26 +2208,31 @@ void CacheEntry::Link(const RefPtr<TextureHandle>& aHandle) {
// TextureHandle as unused and unlinks it from the CacheEntry. The
// entry is removed from its containing Cache, if applicable.
void CacheEntry::Unlink() {
RemoveFromList();
// The entry may not have a valid handle if rasterization failed.
if (mHandle) {
mHandle->SetCacheEntry(nullptr);
mHandle = nullptr;
}
RemoveFromList();
}
// Hashes a path and pattern to a single hash value that can be used for quick
// comparisons. This currently avoids to expensive hashing of internal path
// and pattern data for speed, relying instead on later exact comparisons for
// disambiguation.
HashNumber PathCacheEntry::HashPath(const SkPath& aPath,
HashNumber PathCacheEntry::HashPath(const QuantizedPath& aPath,
const Pattern* aPattern,
const Matrix& aTransform,
const IntRect& aBounds) {
const IntRect& aBounds,
const Point& aOrigin) {
HashNumber hash = 0;
hash = AddToHash(hash, aPath.countVerbs());
hash = AddToHash(hash, aPath.countPoints());
hash = AddToHash(hash, aPath.mPath.num_types);
hash = AddToHash(hash, aPath.mPath.num_points);
// Quantize the relative offset of the path to its bounds.
IntPoint offset = RoundedToInt((aOrigin - Point(aBounds.TopLeft())) * 16.0f);
hash = AddToHash(hash, offset.x);
hash = AddToHash(hash, offset.y);
hash = AddToHash(hash, aBounds.width);
hash = AddToHash(hash, aBounds.height);
if (aPattern) {
@ -2200,7 +2253,7 @@ static inline bool HasMatchingScale(const Matrix& aTransform1,
// Determines if an existing path cache entry matches an incoming path and
// pattern.
inline bool PathCacheEntry::MatchesPath(const SkPath& aPath,
inline bool PathCacheEntry::MatchesPath(const QuantizedPath& aPath,
const Pattern* aPattern,
const StrokeOptions* aStrokeOptions,
const Matrix& aTransform,
@ -2223,13 +2276,13 @@ inline bool PathCacheEntry::MatchesPath(const SkPath& aPath,
aSigma == mSigma;
}
PathCacheEntry::PathCacheEntry(const SkPath& aPath, Pattern* aPattern,
PathCacheEntry::PathCacheEntry(QuantizedPath&& aPath, Pattern* aPattern,
StoredStrokeOptions* aStrokeOptions,
const Matrix& aTransform, const IntRect& aBounds,
const Point& aOrigin, HashNumber aHash,
float aSigma)
: CacheEntryImpl<PathCacheEntry>(aTransform, aBounds, aHash),
mPath(aPath),
mPath(std::move(aPath)),
mOrigin(aOrigin),
mPattern(aPattern),
mStrokeOptions(aStrokeOptions),
@ -2240,11 +2293,11 @@ PathCacheEntry::PathCacheEntry(const SkPath& aPath, Pattern* aPattern,
// texture handle is valid to determine if it will need to render the text run
// or just reuse the cached texture.
already_AddRefed<PathCacheEntry> PathCache::FindOrInsertEntry(
const SkPath& aPath, const Pattern* aPattern,
QuantizedPath aPath, const Pattern* aPattern,
const StrokeOptions* aStrokeOptions, const Matrix& aTransform,
const IntRect& aBounds, const Point& aOrigin, float aSigma) {
HashNumber hash =
PathCacheEntry::HashPath(aPath, aPattern, aTransform, aBounds);
PathCacheEntry::HashPath(aPath, aPattern, aTransform, aBounds, aOrigin);
for (const RefPtr<PathCacheEntry>& entry : GetChain(hash)) {
if (entry->MatchesPath(aPath, aPattern, aStrokeOptions, aTransform, aBounds,
aOrigin, hash, aSigma)) {
@ -2266,8 +2319,8 @@ already_AddRefed<PathCacheEntry> PathCache::FindOrInsertEntry(
}
}
RefPtr<PathCacheEntry> entry =
new PathCacheEntry(aPath, pattern, strokeOptions, aTransform, aBounds,
aOrigin, hash, aSigma);
new PathCacheEntry(std::move(aPath), pattern, strokeOptions, aTransform,
aBounds, aOrigin, hash, aSigma);
Insert(entry);
return entry.forget();
}
@ -2287,6 +2340,154 @@ void DrawTargetWebgl::Fill(const Path* aPath, const Pattern& aPattern,
}
}
QuantizedPath::QuantizedPath(const WGR::Path& aPath) : mPath(aPath) {}
QuantizedPath::QuantizedPath(QuantizedPath&& aPath) noexcept
: mPath(aPath.mPath) {
aPath.mPath.points = nullptr;
aPath.mPath.num_points = 0;
aPath.mPath.types = nullptr;
aPath.mPath.num_types = 0;
}
QuantizedPath::~QuantizedPath() {
if (mPath.points || mPath.types) {
WGR::wgr_path_release(mPath);
}
}
bool QuantizedPath::operator==(const QuantizedPath& aOther) const {
return mPath.num_types == aOther.mPath.num_types &&
mPath.num_points == aOther.mPath.num_points &&
mPath.fill_mode == aOther.mPath.fill_mode &&
!memcmp(mPath.types, aOther.mPath.types,
mPath.num_types * sizeof(uint8_t)) &&
!memcmp(mPath.points, aOther.mPath.points,
mPath.num_points * sizeof(WGR::Point));
}
// Generate a quantized path from the Skia path using WGR. The supplied
// transform will be applied to the path. The path is stored relative to its
// bounds origin to support translation later.
static Maybe<QuantizedPath> GenerateQuantizedPath(const SkPath& aPath,
const IntRect& aBounds,
const Matrix& aTransform) {
WGR::PathBuilder* pb = WGR::wgr_new_builder();
if (!pb) {
return Nothing();
}
WGR::wgr_builder_set_fill_mode(
pb, aPath.getFillType() == SkPath::kWinding_FillType
? WGR::FillMode::Winding
: WGR::FillMode::EvenOdd);
SkPath::RawIter iter(aPath);
SkPoint params[4];
SkPath::Verb currentVerb;
// printf_stderr("bounds: (%d, %d) %d x %d\n", aBounds.x, aBounds.y,
// aBounds.width, aBounds.height);
Matrix transform = aTransform;
transform.PostTranslate(-aBounds.TopLeft());
while ((currentVerb = iter.next(params)) != SkPath::kDone_Verb) {
switch (currentVerb) {
case SkPath::kMove_Verb: {
Point p0 = transform.TransformPoint(SkPointToPoint(params[0]));
// printf_stderr("move (%f, %f)\n", p0.x, p0.y);
WGR::wgr_builder_move_to(pb, p0.x, p0.y);
break;
}
case SkPath::kLine_Verb: {
Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
// printf_stderr("line (%f, %f)\n", p1.x, p1.y);
WGR::wgr_builder_line_to(pb, p1.x, p1.y);
break;
}
case SkPath::kCubic_Verb: {
Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
Point p3 = transform.TransformPoint(SkPointToPoint(params[3]));
// printf_stderr("cubic (%f, %f), (%f, %f), (%f, %f)\n", p1.x, p1.y,
// p2.x, p2.y, p3.x, p3.y);
WGR::wgr_builder_curve_to(pb, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
break;
}
case SkPath::kQuad_Verb: {
Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
// printf_stderr("quad (%f, %f), (%f, %f)\n", p1.x, p1.y, p2.x, p2.y);
WGR::wgr_builder_quad_to(pb, p1.x, p1.y, p2.x, p2.y);
break;
}
case SkPath::kConic_Verb: {
Point p0 = transform.TransformPoint(SkPointToPoint(params[0]));
Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
float w = iter.conicWeight();
std::vector<Point> quads;
int numQuads = ConvertConicToQuads(p0, p1, p2, w, quads);
for (int i = 0; i < numQuads; i++) {
Point q1 = quads[2 * i + 1];
Point q2 = quads[2 * i + 2];
// printf_stderr("conic quad (%f, %f), (%f, %f)\n", q1.x, q1.y, q2.x,
// q2.y);
WGR::wgr_builder_quad_to(pb, q1.x, q1.y, q2.x, q2.y);
}
break;
}
case SkPath::kClose_Verb:
// printf_stderr("close\n");
WGR::wgr_builder_close(pb);
break;
default:
MOZ_ASSERT(false);
// Unexpected verb found in path!
WGR::wgr_builder_release(pb);
return Nothing();
}
}
WGR::Path p = WGR::wgr_builder_get_path(pb);
WGR::wgr_builder_release(pb);
if (!p.num_points || !p.num_types) {
WGR::wgr_path_release(p);
return Nothing();
}
return Some(QuantizedPath(p));
}
// Get the output vertex buffer using WGR from an input quantized path.
static Maybe<WGR::VertexBuffer> GeneratePathVertexBuffer(
const QuantizedPath& aPath, const IntRect& aClipRect) {
WGR::VertexBuffer vb = WGR::wgr_path_rasterize_to_tri_list(
&aPath.mPath, aClipRect.x, aClipRect.y, aClipRect.width,
aClipRect.height);
if (!vb.len) {
WGR::wgr_vertex_buffer_release(vb);
return Nothing();
}
return Some(vb);
}
// Search the path cache for any entries stored in the path vertex buffer and
// remove them.
void PathCache::ClearVertexRanges() {
for (auto& chain : mChains) {
PathCacheEntry* entry = chain.getFirst();
while (entry) {
PathCacheEntry* next = entry->getNext();
if (entry->GetVertexRange().IsValid()) {
entry->Unlink();
}
entry = next;
}
}
}
inline bool DrawTargetWebgl::ShouldAccelPath(const DrawOptions& aOptions) {
return mWebglValid && SupportsDrawOptions(aOptions) && PrepareContext();
}
bool DrawTargetWebgl::SharedContext::DrawPathAccel(
const Path* aPath, const Pattern& aPattern, const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions, const ShadowOptions* aShadow,
@ -2336,8 +2537,15 @@ bool DrawTargetWebgl::SharedContext::DrawPathAccel(
if (!mPathCache) {
mPathCache = MakeUnique<PathCache>();
}
// 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(pathSkia->GetPath(), intBounds, currentTransform);
if (!qp) {
return false;
}
entry = mPathCache->FindOrInsertEntry(
pathSkia->GetPath(), color ? nullptr : &aPattern, aStrokeOptions,
std::move(*qp), color ? nullptr : &aPattern, aStrokeOptions,
currentTransform, intBounds, bounds.TopLeft(),
aShadow ? aShadow->mSigma : -1.0f);
if (!entry) {
@ -2368,77 +2576,163 @@ bool DrawTargetWebgl::SharedContext::DrawPathAccel(
(bounds.TopLeft() - entry->GetOrigin()) + entry->GetBounds().TopLeft();
SurfacePattern pathPattern(nullptr, ExtendMode::CLAMP,
Matrix::Translation(offset), filter);
if (DrawRectAccel(Rect(intBounds), pathPattern, aOptions, shadowColor,
&handle, false, true, true)) {
return true;
return DrawRectAccel(Rect(intBounds), pathPattern, aOptions, shadowColor,
&handle, false, true, true);
}
if (mPathVertexCapacity > 0 && !handle && entry && !aShadow &&
SupportsPattern(aPattern)) {
if (entry->GetVertexRange().IsValid()) {
// If there is a valid cached vertex data in the path vertex buffer, then
// just draw that.
mCurrentTarget->mProfile.OnCacheHit();
return DrawRectAccel(Rect(intBounds.TopLeft(), Size(1, 1)), aPattern,
aOptions, Nothing(), nullptr, false, true, true,
false, nullptr, &entry->GetVertexRange());
}
} else {
// If there isn't a valid texture handle, then we need to rasterize the
// path in a software canvas and upload this to a texture. Solid color
// patterns will be rendered as a path mask that can then be modulated
// with any color. Other pattern types have to rasterize the pattern
// directly into the cached texture.
handle = nullptr;
RefPtr<DrawTargetSkia> pathDT = new DrawTargetSkia;
if (pathDT->Init(intBounds.Size(), color || aShadow
? SurfaceFormat::A8
: SurfaceFormat::B8G8R8A8)) {
Point offset = -intBounds.TopLeft();
if (aShadow) {
// 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));
const Pattern& cachePattern = color ? maskPattern : aPattern;
// 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);
}
if (aShadow && aShadow->mSigma > 0.0f) {
// Blur the shadow if required.
uint8_t* data = nullptr;
IntSize size;
int32_t stride = 0;
SurfaceFormat format = SurfaceFormat::UNKNOWN;
if (pathDT->LockBits(&data, &size, &stride, &format)) {
AlphaBoxBlur blur(Rect(pathDT->GetRect()), stride, aShadow->mSigma,
aShadow->mSigma);
blur.Blur(data);
pathDT->ReleaseBits(data);
// printf_stderr("Generating... verbs %d, points %d\n",
// int(pathSkia->GetPath().countVerbs()),
// int(pathSkia->GetPath().countPoints()));
Maybe<WGR::VertexBuffer> vb;
if (aStrokeOptions) {
// If stroking, then generate a path to fill the stroked region. This
// path will need to be quantized again because it differs from the path
// used for the cache entry, but this allows us to avoid generating a
// fill path on a cache hit.
SkPaint paint;
if (StrokeOptionsToPaint(paint, *aStrokeOptions)) {
Maybe<SkRect> cullRect;
Matrix invTransform = currentTransform;
if (invTransform.Invert()) {
// Transform the stroking clip rect from device space to local space.
Rect invRect = invTransform.TransformBounds(Rect(mClipRect));
invRect.RoundOut();
cullRect = Some(RectToSkRect(invRect));
}
}
RefPtr<SourceSurface> pathSurface = pathDT->Snapshot();
if (pathSurface) {
// If the target changed, try to restore it.
if (mCurrentTarget != oldTarget && !oldTarget->PrepareContext()) {
return false;
}
SurfacePattern pathPattern(pathSurface, ExtendMode::CLAMP,
Matrix::Translation(intBounds.TopLeft()),
filter);
// Try and upload the rasterized path to a texture. If there is a
// valid texture handle after this, then link it to the entry.
// Otherwise, we might have to fall back to software drawing the
// path, so unlink it from the entry.
if (DrawRectAccel(Rect(intBounds), pathPattern, aOptions, shadowColor,
&handle, false, true) &&
handle) {
if (entry) {
entry->Link(handle);
SkPath fillPath;
if (paint.getFillPath(pathSkia->GetPath(), &fillPath,
cullRect.ptrOr(nullptr),
ComputeResScaleForStroking(currentTransform))) {
// printf_stderr(" stroke fill... verbs %d, points %d\n",
// int(fillPath.countVerbs()),
// int(fillPath.countPoints()));
if (Maybe<QuantizedPath> qp = GenerateQuantizedPath(
fillPath, intBounds, currentTransform)) {
vb = GeneratePathVertexBuffer(
*qp, IntRect(-intBounds.TopLeft(), mViewportSize));
}
} else if (entry) {
entry->Unlink();
}
return true;
}
} else {
vb = GeneratePathVertexBuffer(
entry->GetPath(), IntRect(-intBounds.TopLeft(), mViewportSize));
}
if (vb) {
uint32_t vertexBytes = vb->len * sizeof(WGR::OutputVertex);
// printf_stderr(" ... %d verts, %d bytes\n", int(vb->len),
// int(vertexBytes));
if (vertexBytes > mPathVertexCapacity - mPathVertexOffset &&
vertexBytes <= mPathVertexCapacity - sizeof(kRectVertexData)) {
// If the vertex data is too large to fit in the remaining path vertex
// buffer, then orphan the contents of the vertex buffer to make room
// for it.
if (mPathCache) {
mPathCache->ClearVertexRanges();
}
ResetPathVertexBuffer();
}
if (vertexBytes <= mPathVertexCapacity - mPathVertexOffset) {
// If there is actually room to fit the vertex data in the vertex buffer
// after orphaning as necessary, then upload the data to the next
// available offset in the buffer.
PathVertexRange vertexRange(
uint32_t(mPathVertexOffset / sizeof(WGR::OutputVertex)),
uint32_t(vb->len));
if (entry) {
entry->SetVertexRange(vertexRange);
}
// printf_stderr(" ... offset %d\n", mPathVertexOffset);
mWebgl->RawBufferSubData(LOCAL_GL_ARRAY_BUFFER, mPathVertexOffset,
(const uint8_t*)vb->data, vertexBytes);
mPathVertexOffset += vertexBytes;
wgr_vertex_buffer_release(vb.ref());
// Finally, draw the uploaded vertex data.
mCurrentTarget->mProfile.OnCacheMiss();
return DrawRectAccel(Rect(intBounds.TopLeft(), Size(1, 1)), aPattern,
aOptions, Nothing(), nullptr, false, true, true,
false, nullptr, &vertexRange);
}
wgr_vertex_buffer_release(vb.ref());
// If we failed to draw the vertex data for some reason, then fall through
// to the texture rasterization path.
}
}
// If there isn't a valid texture handle, then we need to rasterize the
// path in a software canvas and upload this to a texture. Solid color
// patterns will be rendered as a path mask that can then be modulated
// with any color. Other pattern types have to rasterize the pattern
// directly into the cached texture.
handle = nullptr;
RefPtr<DrawTargetSkia> pathDT = new DrawTargetSkia;
if (pathDT->Init(intBounds.Size(), color || aShadow
? SurfaceFormat::A8
: SurfaceFormat::B8G8R8A8)) {
Point offset = -intBounds.TopLeft();
if (aShadow) {
// 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));
const Pattern& cachePattern = color ? maskPattern : aPattern;
// 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);
}
if (aShadow && aShadow->mSigma > 0.0f) {
// Blur the shadow if required.
uint8_t* data = nullptr;
IntSize size;
int32_t stride = 0;
SurfaceFormat format = SurfaceFormat::UNKNOWN;
if (pathDT->LockBits(&data, &size, &stride, &format)) {
AlphaBoxBlur blur(Rect(pathDT->GetRect()), stride, aShadow->mSigma,
aShadow->mSigma);
blur.Blur(data);
pathDT->ReleaseBits(data);
}
}
RefPtr<SourceSurface> pathSurface = pathDT->Snapshot();
if (pathSurface) {
// If the target changed, try to restore it.
if (mCurrentTarget != oldTarget && !oldTarget->PrepareContext()) {
return false;
}
SurfacePattern pathPattern(pathSurface, ExtendMode::CLAMP,
Matrix::Translation(intBounds.TopLeft()),
filter);
// Try and upload the rasterized path to a texture. If there is a
// valid texture handle after this, then link it to the entry.
// Otherwise, we might have to fall back to software drawing the
// path, so unlink it from the entry.
if (DrawRectAccel(Rect(intBounds), pathPattern, aOptions, shadowColor,
&handle, false, true) &&
handle) {
if (entry) {
entry->Link(handle);
}
} else if (entry) {
entry->Unlink();
}
return true;
}
}
@ -2450,7 +2744,7 @@ void DrawTargetWebgl::DrawPath(const Path* aPath, const Pattern& aPattern,
const StrokeOptions* aStrokeOptions) {
// If there is a WebGL context, then try to cache the path to avoid slow
// fallbacks.
if (mWebglValid && SupportsDrawOptions(aOptions) && PrepareContext() &&
if (ShouldAccelPath(aOptions) &&
mSharedContext->DrawPathAccel(aPath, aPattern, aOptions,
aStrokeOptions)) {
return;
@ -2545,7 +2839,7 @@ void DrawTargetWebgl::DrawShadow(const Path* aPath, const Pattern& aPattern,
const StrokeOptions* aStrokeOptions) {
// If there is a WebGL context, then try to cache the path to avoid slow
// fallbacks.
if (mWebglValid && SupportsDrawOptions(aOptions) && PrepareContext() &&
if (ShouldAccelPath(aOptions) &&
mSharedContext->DrawPathAccel(aPath, aPattern, aOptions, aStrokeOptions,
&aShadow)) {
return;
@ -2562,7 +2856,7 @@ void DrawTargetWebgl::DrawSurfaceWithShadow(SourceSurface* aSurface,
const ShadowOptions& aShadow,
CompositionOp aOperator) {
DrawOptions options(1.0f, aOperator);
if (mWebglValid && SupportsDrawOptions(options) && PrepareContext()) {
if (ShouldAccelPath(options)) {
SurfacePattern pattern(aSurface, ExtendMode::CLAMP,
Matrix::Translation(aDest));
SkPath skiaPath;
@ -3384,6 +3678,19 @@ bool DrawTargetWebgl::UsageProfile::RequiresRefresh() const {
return mFailedFrames > failRatio * mFrameCount;
}
void DrawTargetWebgl::SharedContext::CachePrefs() {
uint32_t capacity = StaticPrefs::gfx_canvas_accelerated_gpu_path_size() << 20;
if (capacity != mPathVertexCapacity) {
mPathVertexCapacity = capacity;
if (mPathCache) {
mPathCache->ClearVertexRanges();
}
if (mPathVertexBuffer) {
ResetPathVertexBuffer();
}
}
}
// For use within CanvasRenderingContext2D, called on BorrowDrawTarget.
void DrawTargetWebgl::BeginFrame(const IntRect& aPersistedRect) {
if (mNeedsPresent) {
@ -3401,6 +3708,8 @@ void DrawTargetWebgl::BeginFrame(const IntRect& aPersistedRect) {
}
// Check if we need to clear out any cached because of memory pressure.
mSharedContext->ClearCachesIfNecessary();
// Cache any prefs for the frame.
mSharedContext->CachePrefs();
mProfile.BeginFrame();
}

View File

@ -45,6 +45,7 @@ class SharedTextureHandle;
class StandaloneTexture;
class GlyphCache;
class PathCache;
struct PathVertexRange;
// DrawTargetWebgl implements a subset of the DrawTarget API suitable for use
// by CanvasRenderingContext2D. It maps these to a client WebGL context so that
@ -162,8 +163,12 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
bool mDirtyAA = true;
// WebGL shader resources
RefPtr<WebGLBufferJS> mVertexBuffer;
RefPtr<WebGLVertexArrayJS> mVertexArray;
RefPtr<WebGLBufferJS> mPathVertexBuffer;
RefPtr<WebGLVertexArrayJS> mPathVertexArray;
// The current insertion offset into the GPU path buffer.
uint32_t mPathVertexOffset = 0;
// The maximum size of the GPU path buffer.
uint32_t mPathVertexCapacity = 0;
RefPtr<WebGLProgramJS> mSolidProgram;
RefPtr<WebGLUniformLocationJS> mSolidProgramViewport;
RefPtr<WebGLUniformLocationJS> mSolidProgramAA;
@ -234,6 +239,7 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
bool Initialize();
bool CreateShaders();
void ResetPathVertexBuffer();
void SetBlendState(CompositionOp aOp,
const Maybe<DeviceColor>& aBlendColor = Nothing());
@ -281,7 +287,8 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
RefPtr<TextureHandle>* aHandle = nullptr,
bool aTransformed = true, bool aClipped = true,
bool aAccelOnly = false, bool aForceUpdate = false,
const StrokeOptions* aStrokeOptions = nullptr);
const StrokeOptions* aStrokeOptions = nullptr,
const PathVertexRange* aVertexRange = nullptr);
bool DrawPathAccel(const Path* aPath, const Pattern& aPattern,
const DrawOptions& aOptions,
@ -310,6 +317,8 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
void ClearCachesIfNecessary();
void WaitForShmem(DrawTargetWebgl* aTarget);
void CachePrefs();
};
RefPtr<SharedContext> mSharedContext;
@ -474,6 +483,8 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
bool aTransformed = true, bool aClipped = true,
bool aAccelOnly = false, bool aForceUpdate = false,
const StrokeOptions* aStrokeOptions = nullptr);
bool ShouldAccelPath(const DrawOptions& aOptions);
void DrawPath(const Path* aPath, const Pattern& aPattern,
const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions = nullptr);

View File

@ -11,6 +11,7 @@
#include "mozilla/HashFunctions.h"
#include "mozilla/gfx/PathSkia.h"
#include "mozilla/gfx/WPFGpuRaster.h"
namespace mozilla::gfx {
@ -108,6 +109,7 @@ class CacheEntryImpl : public CacheEntry, public LinkedListElement<RefPtr<T>> {
// CacheImpl manages a list of CacheEntry.
template <typename T>
class CacheImpl {
protected:
typedef LinkedList<RefPtr<T>> ListType;
static constexpr size_t kNumChains = 17;
@ -369,33 +371,62 @@ class GlyphCache : public LinkedListElement<GlyphCache>,
ScaledFont* mFont;
};
struct QuantizedPath {
explicit QuantizedPath(const WGR::Path& aPath);
// Ensure the path can only be moved, but not copied.
QuantizedPath(QuantizedPath&&) noexcept;
QuantizedPath(const QuantizedPath&) = delete;
~QuantizedPath();
bool operator==(const QuantizedPath&) const;
WGR::Path mPath;
};
struct PathVertexRange {
uint32_t mOffset;
uint32_t mLength;
PathVertexRange() : mOffset(0), mLength(0) {}
PathVertexRange(uint32_t aOffset, uint32_t aLength)
: mOffset(aOffset), mLength(aLength) {}
bool IsValid() const { return mLength > 0; }
};
// PathCacheEntry stores a rasterized version of a supplied path with a given
// pattern.
class PathCacheEntry : public CacheEntryImpl<PathCacheEntry> {
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathCacheEntry, override)
PathCacheEntry(const SkPath& aPath, Pattern* aPattern,
PathCacheEntry(QuantizedPath&& aPath, Pattern* aPattern,
StoredStrokeOptions* aStrokeOptions, const Matrix& aTransform,
const IntRect& aBounds, const Point& aOrigin, HashNumber aHash,
float aSigma = -1.0f);
bool MatchesPath(const SkPath& aPath, const Pattern* aPattern,
bool MatchesPath(const QuantizedPath& aPath, const Pattern* aPattern,
const StrokeOptions* aStrokeOptions,
const Matrix& aTransform, const IntRect& aBounds,
const Point& aOrigin, HashNumber aHash, float aSigma);
static HashNumber HashPath(const SkPath& aPath, const Pattern* aPattern,
const Matrix& aTransform, const IntRect& aBounds);
static HashNumber HashPath(const QuantizedPath& aPath,
const Pattern* aPattern, const Matrix& aTransform,
const IntRect& aBounds, const Point& aOrigin);
const QuantizedPath& GetPath() const { return mPath; }
const Point& GetOrigin() const { return mOrigin; }
// Valid if either a mask (no pattern) or there is valid pattern.
bool IsValid() const override { return !mPattern || mPattern->IsValid(); }
const PathVertexRange& GetVertexRange() const { return mVertexRange; }
void SetVertexRange(const PathVertexRange& aRange) { mVertexRange = aRange; }
private:
// The actual path geometry supplied
SkPath mPath;
QuantizedPath mPath;
// The transformed origin of the path
Point mOrigin;
// The pattern used to rasterize the path, if not a mask
@ -404,6 +435,8 @@ class PathCacheEntry : public CacheEntryImpl<PathCacheEntry> {
UniquePtr<StoredStrokeOptions> mStrokeOptions;
// The shadow blur sigma
float mSigma;
// If the path has cached geometry in the vertex buffer.
PathVertexRange mVertexRange;
};
class PathCache : public CacheImpl<PathCacheEntry> {
@ -411,9 +444,11 @@ class PathCache : public CacheImpl<PathCacheEntry> {
PathCache() = default;
already_AddRefed<PathCacheEntry> FindOrInsertEntry(
const SkPath& aPath, const Pattern* aPattern,
QuantizedPath aPath, const Pattern* aPattern,
const StrokeOptions* aStrokeOptions, const Matrix& aTransform,
const IntRect& aBounds, const Point& aOrigin, float aSigma = -1.0f);
void ClearVertexRanges();
};
} // namespace mozilla::gfx

View File

@ -332,6 +332,21 @@ static inline bool IsBackedByPixels(const SkCanvas* aCanvas) {
return true;
}
/**
* Computes appropriate resolution scale to be used with SkPath::getFillPath
* based on the scaling of the supplied transform.
*/
float ComputeResScaleForStroking(const Matrix& aTransform);
/**
* This is a wrapper around SkGeometry's SkConic that can be used to convert
* conic sections in an SkPath to a sequence of quadratic curves. The quads
* vector is organized such that for the Nth quad, it's control points are
* 2*N, 2*N+1, 2*N+2. This function returns the resulting number of quads.
*/
int ConvertConicToQuads(const Point& aP0, const Point& aP1, const Point& aP2,
float aWeight, std::vector<Point>& aQuads);
} // namespace gfx
} // namespace mozilla

View File

@ -8,6 +8,7 @@
#include "HelpersSkia.h"
#include "PathHelpers.h"
#include "skia/src/core/SkDraw.h"
#include "skia/src/core/SkGeometry.h"
namespace mozilla::gfx {
@ -124,6 +125,12 @@ bool PathSkia::ContainsPoint(const Point& aPoint,
return SkPathContainsPoint(mPath, aPoint, aTransform);
}
float ComputeResScaleForStroking(const Matrix& aTransform) {
SkMatrix skiaMatrix;
GfxMatrixToSkiaMatrix(aTransform, skiaMatrix);
return SkDraw::ComputeResScaleForStroking(skiaMatrix);
}
bool PathSkia::StrokeContainsPoint(const StrokeOptions& aStrokeOptions,
const Point& aPoint,
const Matrix& aTransform) const {
@ -136,11 +143,9 @@ bool PathSkia::StrokeContainsPoint(const StrokeOptions& aStrokeOptions,
return false;
}
SkMatrix skiaMatrix;
GfxMatrixToSkiaMatrix(aTransform, skiaMatrix);
SkPath strokePath;
paint.getFillPath(mPath, &strokePath, nullptr,
SkDraw::ComputeResScaleForStroking(skiaMatrix));
ComputeResScaleForStroking(aTransform));
return SkPathContainsPoint(strokePath, aPoint, aTransform);
}
@ -192,6 +197,20 @@ Rect PathSkia::GetFastBounds(const Matrix& aTransform,
return aTransform.TransformBounds(SkRectToRect(bounds));
}
int ConvertConicToQuads(const Point& aP0, const Point& aP1, const Point& aP2,
float aWeight, std::vector<Point>& aQuads) {
SkConic conic(PointToSkPoint(aP0), PointToSkPoint(aP1), PointToSkPoint(aP2),
aWeight);
int pow2 = conic.computeQuadPOW2(0.25f);
aQuads.resize(1 + 2 * (1 << pow2));
int numQuads =
conic.chopIntoQuadsPOW2(reinterpret_cast<SkPoint*>(&aQuads[0]), pow2);
if (numQuads < 1 << pow2) {
aQuads.resize(1 + 2 * numQuads);
}
return numQuads;
}
void PathSkia::StreamToSink(PathSink* aSink) const {
SkPath::RawIter iter(mPath);
@ -213,6 +232,16 @@ void PathSkia::StreamToSink(PathSink* aSink) const {
aSink->QuadraticBezierTo(SkPointToPoint(points[1]),
SkPointToPoint(points[2]));
break;
case SkPath::kConic_Verb: {
std::vector<Point> quads;
int numQuads = ConvertConicToQuads(
SkPointToPoint(points[0]), SkPointToPoint(points[1]),
SkPointToPoint(points[2]), iter.conicWeight(), quads);
for (int i = 0; i < numQuads; i++) {
aSink->QuadraticBezierTo(quads[2 * i + 1], quads[2 * i + 2]);
}
break;
}
case SkPath::kClose_Verb:
aSink->Close();
break;

View File

@ -5592,6 +5592,11 @@
value: 0.66
mirror: always
- name: gfx.canvas.accelerated.gpu-path-size
type: RelaxedAtomicUint32
value: 4
mirror: always
# 0x7fff is the maximum supported xlib surface size and is more than enough for canvases.
- name: gfx.canvas.max-size
type: RelaxedAtomicInt32