Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / canvas / DrawTargetWebgl.cpp
blobfdc45cc8420ba09aeaccdee2b5e340dc1e2cf0fc
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "DrawTargetWebglInternal.h"
8 #include "SourceSurfaceWebgl.h"
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/StaticPrefs_gfx.h"
12 #include "mozilla/gfx/AAStroke.h"
13 #include "mozilla/gfx/Blur.h"
14 #include "mozilla/gfx/DrawTargetSkia.h"
15 #include "mozilla/gfx/gfxVars.h"
16 #include "mozilla/gfx/Helpers.h"
17 #include "mozilla/gfx/HelpersSkia.h"
18 #include "mozilla/gfx/Logging.h"
19 #include "mozilla/gfx/PathHelpers.h"
20 #include "mozilla/gfx/PathSkia.h"
21 #include "mozilla/gfx/Swizzle.h"
22 #include "mozilla/layers/ImageDataSerializer.h"
23 #include "mozilla/layers/RemoteTextureMap.h"
24 #include "mozilla/widget/ScreenManager.h"
25 #include "skia/include/core/SkPixmap.h"
26 #include "nsContentUtils.h"
28 #include "GLContext.h"
29 #include "WebGLContext.h"
30 #include "WebGLChild.h"
31 #include "WebGLBuffer.h"
32 #include "WebGLFramebuffer.h"
33 #include "WebGLProgram.h"
34 #include "WebGLShader.h"
35 #include "WebGLTexture.h"
36 #include "WebGLVertexArray.h"
38 #include "gfxPlatform.h"
40 namespace mozilla::gfx {
42 // Inserts (allocates) a rectangle of the requested size into the tree.
43 Maybe<IntPoint> TexturePacker::Insert(const IntSize& aSize) {
44 // Check if the available space could possibly fit the requested size. If
45 // not, there is no reason to continue searching within this sub-tree.
46 if (mAvailable < std::min(aSize.width, aSize.height) ||
47 mBounds.width < aSize.width || mBounds.height < aSize.height) {
48 return Nothing();
50 if (mChildren) {
51 // If this node has children, then try to insert into each of the children
52 // in turn.
53 Maybe<IntPoint> inserted = mChildren[0].Insert(aSize);
54 if (!inserted) {
55 inserted = mChildren[1].Insert(aSize);
57 // If the insertion succeeded, adjust the available state to reflect the
58 // remaining space in the children.
59 if (inserted) {
60 mAvailable = std::max(mChildren[0].mAvailable, mChildren[1].mAvailable);
61 if (!mAvailable) {
62 DiscardChildren();
65 return inserted;
67 // If we get here, we've encountered a leaf node. First check if its size is
68 // exactly the requested size. If so, mark the node as unavailable and return
69 // its offset.
70 if (mBounds.Size() == aSize) {
71 mAvailable = 0;
72 return Some(mBounds.TopLeft());
74 // The node is larger than the requested size. Choose the axis which has the
75 // most excess space beyond the requested size and split it so that at least
76 // one of the children matches the requested size for that axis.
77 if (mBounds.width - aSize.width > mBounds.height - aSize.height) {
78 mChildren.reset(new TexturePacker[2]{
79 TexturePacker(
80 IntRect(mBounds.x, mBounds.y, aSize.width, mBounds.height)),
81 TexturePacker(IntRect(mBounds.x + aSize.width, mBounds.y,
82 mBounds.width - aSize.width, mBounds.height))});
83 } else {
84 mChildren.reset(new TexturePacker[2]{
85 TexturePacker(
86 IntRect(mBounds.x, mBounds.y, mBounds.width, aSize.height)),
87 TexturePacker(IntRect(mBounds.x, mBounds.y + aSize.height,
88 mBounds.width, mBounds.height - aSize.height))});
90 // After splitting, try to insert into the first child, which should usually
91 // be big enough to accomodate the request. Adjust the available state to the
92 // remaining space.
93 Maybe<IntPoint> inserted = mChildren[0].Insert(aSize);
94 mAvailable = std::max(mChildren[0].mAvailable, mChildren[1].mAvailable);
95 return inserted;
98 // Removes (frees) a rectangle with the given bounds from the tree.
99 bool TexturePacker::Remove(const IntRect& aBounds) {
100 if (!mChildren) {
101 // If there are no children, we encountered a leaf node. Non-zero available
102 // state means that this node was already removed previously. Also, if the
103 // bounds don't contain the request, and assuming the tree was previously
104 // split during insertion, then this node is not the node we're searching
105 // for.
106 if (mAvailable > 0 || !mBounds.Contains(aBounds)) {
107 return false;
109 // The bounds match exactly and it was previously inserted, so in this case
110 // we can just remove it.
111 if (mBounds == aBounds) {
112 mAvailable = std::min(mBounds.width, mBounds.height);
113 return true;
115 // We need to split this leaf node so that it can exactly match the removed
116 // bounds. We know the leaf node at least contains the removed bounds, but
117 // needs to be subdivided until it has a child node that exactly matches.
118 // Choose the axis to split with the largest amount of excess space. Within
119 // that axis, choose the larger of the space before or after the subrect as
120 // the split point to the new children.
121 if (mBounds.width - aBounds.width > mBounds.height - aBounds.height) {
122 int split = aBounds.x - mBounds.x > mBounds.XMost() - aBounds.XMost()
123 ? aBounds.x
124 : aBounds.XMost();
125 mChildren.reset(new TexturePacker[2]{
126 TexturePacker(
127 IntRect(mBounds.x, mBounds.y, split - mBounds.x, mBounds.height),
128 false),
129 TexturePacker(IntRect(split, mBounds.y, mBounds.XMost() - split,
130 mBounds.height),
131 false)});
132 } else {
133 int split = aBounds.y - mBounds.y > mBounds.YMost() - aBounds.YMost()
134 ? aBounds.y
135 : aBounds.YMost();
136 mChildren.reset(new TexturePacker[2]{
137 TexturePacker(
138 IntRect(mBounds.x, mBounds.y, mBounds.width, split - mBounds.y),
139 false),
140 TexturePacker(
141 IntRect(mBounds.x, split, mBounds.width, mBounds.YMost() - split),
142 false)});
145 // We've encountered a branch node. Determine which of the two child nodes
146 // would possibly contain the removed bounds. We first check which axis the
147 // children were split on and then whether the removed bounds on that axis
148 // are past the start of the second child. Proceed to recurse into that
149 // child node for removal.
150 bool next = mChildren[0].mBounds.x < mChildren[1].mBounds.x
151 ? aBounds.x >= mChildren[1].mBounds.x
152 : aBounds.y >= mChildren[1].mBounds.y;
153 bool removed = mChildren[next ? 1 : 0].Remove(aBounds);
154 if (removed) {
155 if (mChildren[0].IsFullyAvailable() && mChildren[1].IsFullyAvailable()) {
156 DiscardChildren();
157 mAvailable = std::min(mBounds.width, mBounds.height);
158 } else {
159 mAvailable = std::max(mChildren[0].mAvailable, mChildren[1].mAvailable);
162 return removed;
165 BackingTexture::BackingTexture(const IntSize& aSize, SurfaceFormat aFormat,
166 const RefPtr<WebGLTexture>& aTexture)
167 : mSize(aSize), mFormat(aFormat), mTexture(aTexture) {}
169 SharedTexture::SharedTexture(const IntSize& aSize, SurfaceFormat aFormat,
170 const RefPtr<WebGLTexture>& aTexture)
171 : BackingTexture(aSize, aFormat, aTexture),
172 mPacker(IntRect(IntPoint(0, 0), aSize)) {}
174 SharedTextureHandle::SharedTextureHandle(const IntRect& aBounds,
175 SharedTexture* aTexture)
176 : mBounds(aBounds), mTexture(aTexture) {}
178 already_AddRefed<SharedTextureHandle> SharedTexture::Allocate(
179 const IntSize& aSize) {
180 RefPtr<SharedTextureHandle> handle;
181 if (Maybe<IntPoint> origin = mPacker.Insert(aSize)) {
182 handle = new SharedTextureHandle(IntRect(*origin, aSize), this);
183 ++mAllocatedHandles;
185 return handle.forget();
188 bool SharedTexture::Free(const SharedTextureHandle& aHandle) {
189 if (aHandle.mTexture != this) {
190 return false;
192 if (!mPacker.Remove(aHandle.mBounds)) {
193 return false;
195 --mAllocatedHandles;
196 return true;
199 StandaloneTexture::StandaloneTexture(const IntSize& aSize,
200 SurfaceFormat aFormat,
201 const RefPtr<WebGLTexture>& aTexture)
202 : BackingTexture(aSize, aFormat, aTexture) {}
204 DrawTargetWebgl::DrawTargetWebgl() = default;
206 inline void SharedContextWebgl::ClearLastTexture(bool aFullClear) {
207 mLastTexture = nullptr;
208 if (aFullClear) {
209 mLastClipMask = nullptr;
213 // Attempts to clear the snapshot state. If the snapshot is only referenced by
214 // this target, then it should simply be destroyed. If it is a WebGL surface in
215 // use by something else, then special cleanup such as reusing the texture or
216 // copy-on-write may be possible.
217 void DrawTargetWebgl::ClearSnapshot(bool aCopyOnWrite, bool aNeedHandle) {
218 if (!mSnapshot) {
219 return;
221 mSharedContext->ClearLastTexture();
222 RefPtr<SourceSurfaceWebgl> snapshot = mSnapshot.forget();
223 if (snapshot->hasOneRef()) {
224 return;
226 if (aCopyOnWrite) {
227 // WebGL snapshots must be notified that the framebuffer contents will be
228 // changing so that it can copy the data.
229 snapshot->DrawTargetWillChange(aNeedHandle);
230 } else {
231 // If not copying, then give the backing texture to the surface for reuse.
232 snapshot->GiveTexture(
233 mSharedContext->WrapSnapshot(GetSize(), GetFormat(), mTex.forget()));
237 DrawTargetWebgl::~DrawTargetWebgl() {
238 ClearSnapshot(false);
239 if (mSharedContext) {
240 // Force any Skia snapshots to copy the shmem before it deallocs.
241 if (mSkia) {
242 mSkia->DetachAllSnapshots();
244 mSharedContext->ClearLastTexture(true);
245 mClipMask = nullptr;
246 mFramebuffer = nullptr;
247 mTex = nullptr;
248 mSharedContext->mDrawTargetCount--;
252 SharedContextWebgl::SharedContextWebgl() = default;
254 SharedContextWebgl::~SharedContextWebgl() {
255 // Detect context loss before deletion.
256 if (mWebgl) {
257 ExitTlsScope();
258 mWebgl->ActiveTexture(0);
260 if (mWGRPathBuilder) {
261 WGR::wgr_builder_release(mWGRPathBuilder);
262 mWGRPathBuilder = nullptr;
264 ClearAllTextures();
265 UnlinkSurfaceTextures();
266 UnlinkGlyphCaches();
269 gl::GLContext* SharedContextWebgl::GetGLContext() {
270 return mWebgl ? mWebgl->GL() : nullptr;
273 void SharedContextWebgl::EnterTlsScope() {
274 if (mTlsScope.isSome()) {
275 return;
277 if (gl::GLContext* gl = GetGLContext()) {
278 mTlsScope = Some(gl->mUseTLSIsCurrent);
279 gl::GLContext::InvalidateCurrentContext();
280 gl->mUseTLSIsCurrent = true;
284 void SharedContextWebgl::ExitTlsScope() {
285 if (mTlsScope.isNothing()) {
286 return;
288 if (gl::GLContext* gl = GetGLContext()) {
289 gl->mUseTLSIsCurrent = mTlsScope.value();
291 mTlsScope = Nothing();
294 // Remove any SourceSurface user data associated with this TextureHandle.
295 inline void SharedContextWebgl::UnlinkSurfaceTexture(
296 const RefPtr<TextureHandle>& aHandle) {
297 if (RefPtr<SourceSurface> surface = aHandle->GetSurface()) {
298 // Ensure any WebGL snapshot textures get unlinked.
299 if (surface->GetType() == SurfaceType::WEBGL) {
300 static_cast<SourceSurfaceWebgl*>(surface.get())->OnUnlinkTexture(this);
302 surface->RemoveUserData(aHandle->IsShadow() ? &mShadowTextureKey
303 : &mTextureHandleKey);
307 // Unlinks TextureHandles from any SourceSurface user data.
308 void SharedContextWebgl::UnlinkSurfaceTextures() {
309 for (RefPtr<TextureHandle> handle = mTextureHandles.getFirst(); handle;
310 handle = handle->getNext()) {
311 UnlinkSurfaceTexture(handle);
315 // Unlinks GlyphCaches from any ScaledFont user data.
316 void SharedContextWebgl::UnlinkGlyphCaches() {
317 GlyphCache* cache = mGlyphCaches.getFirst();
318 while (cache) {
319 ScaledFont* font = cache->GetFont();
320 // Access the next cache before removing the user data, as it might destroy
321 // the cache.
322 cache = cache->getNext();
323 font->RemoveUserData(&mGlyphCacheKey);
327 void SharedContextWebgl::OnMemoryPressure() { mShouldClearCaches = true; }
329 void SharedContextWebgl::ClearCaches() {
330 OnMemoryPressure();
331 ClearCachesIfNecessary();
334 // Clear out the entire list of texture handles from any source.
335 void SharedContextWebgl::ClearAllTextures() {
336 while (!mTextureHandles.isEmpty()) {
337 PruneTextureHandle(mTextureHandles.popLast());
338 --mNumTextureHandles;
342 // Scan through the shared texture pages looking for any that are empty and
343 // delete them.
344 void SharedContextWebgl::ClearEmptyTextureMemory() {
345 for (auto pos = mSharedTextures.begin(); pos != mSharedTextures.end();) {
346 if (!(*pos)->HasAllocatedHandles()) {
347 RefPtr<SharedTexture> shared = *pos;
348 size_t usedBytes = shared->UsedBytes();
349 mEmptyTextureMemory -= usedBytes;
350 mTotalTextureMemory -= usedBytes;
351 pos = mSharedTextures.erase(pos);
352 } else {
353 ++pos;
358 // If there is a request to clear out the caches because of memory pressure,
359 // then first clear out all the texture handles in the texture cache. If there
360 // are still empty texture pages being kept around, then clear those too.
361 void SharedContextWebgl::ClearCachesIfNecessary() {
362 if (!mShouldClearCaches.exchange(false)) {
363 return;
365 mZeroBuffer = nullptr;
366 ClearAllTextures();
367 if (mEmptyTextureMemory) {
368 ClearEmptyTextureMemory();
370 ClearLastTexture();
373 // Try to initialize a new WebGL context. Verifies that the requested size does
374 // not exceed the available texture limits and that shader creation succeeded.
375 bool DrawTargetWebgl::Init(const IntSize& size, const SurfaceFormat format,
376 const RefPtr<SharedContextWebgl>& aSharedContext) {
377 MOZ_ASSERT(format == SurfaceFormat::B8G8R8A8 ||
378 format == SurfaceFormat::B8G8R8X8);
380 mSize = size;
381 mFormat = format;
383 if (!aSharedContext || aSharedContext->IsContextLost() ||
384 aSharedContext->mDrawTargetCount >=
385 StaticPrefs::gfx_canvas_accelerated_max_draw_target_count()) {
386 return false;
388 mSharedContext = aSharedContext;
389 mSharedContext->mDrawTargetCount++;
391 if (size_t(std::max(size.width, size.height)) >
392 mSharedContext->mMaxTextureSize) {
393 return false;
396 if (!CreateFramebuffer()) {
397 return false;
400 size_t byteSize = layers::ImageDataSerializer::ComputeRGBBufferSize(
401 mSize, SurfaceFormat::B8G8R8A8);
402 if (byteSize == 0) {
403 return false;
406 size_t shmemSize = mozilla::ipc::SharedMemory::PageAlignedSize(byteSize);
407 if (NS_WARN_IF(shmemSize > UINT32_MAX)) {
408 MOZ_ASSERT_UNREACHABLE("Buffer too big?");
409 return false;
412 auto shmem = MakeRefPtr<mozilla::ipc::SharedMemoryBasic>();
413 if (NS_WARN_IF(!shmem->Create(shmemSize)) ||
414 NS_WARN_IF(!shmem->Map(shmemSize))) {
415 return false;
418 mShmem = std::move(shmem);
419 mShmemSize = shmemSize;
421 mSkia = new DrawTargetSkia;
422 auto stride = layers::ImageDataSerializer::ComputeRGBStride(
423 SurfaceFormat::B8G8R8A8, size.width);
424 if (!mSkia->Init(reinterpret_cast<uint8_t*>(mShmem->memory()), size, stride,
425 SurfaceFormat::B8G8R8A8, true)) {
426 return false;
429 // Allocate an unclipped copy of the DT pointing to its data.
430 uint8_t* dtData = nullptr;
431 IntSize dtSize;
432 int32_t dtStride = 0;
433 SurfaceFormat dtFormat = SurfaceFormat::UNKNOWN;
434 if (!mSkia->LockBits(&dtData, &dtSize, &dtStride, &dtFormat)) {
435 return false;
437 mSkiaNoClip = new DrawTargetSkia;
438 if (!mSkiaNoClip->Init(dtData, dtSize, dtStride, dtFormat, true)) {
439 mSkia->ReleaseBits(dtData);
440 return false;
442 mSkia->ReleaseBits(dtData);
444 SetPermitSubpixelAA(IsOpaque(format));
445 return true;
448 // If a non-recoverable error occurred that would stop the canvas from initing.
449 static Atomic<bool> sContextInitError(false);
451 already_AddRefed<SharedContextWebgl> SharedContextWebgl::Create() {
452 // If context initialization would fail, don't even try to create a context.
453 if (sContextInitError) {
454 return nullptr;
456 RefPtr<SharedContextWebgl> sharedContext = new SharedContextWebgl;
457 if (!sharedContext->Initialize()) {
458 return nullptr;
460 return sharedContext.forget();
463 bool SharedContextWebgl::Initialize() {
464 WebGLContextOptions options = {};
465 options.alpha = true;
466 options.depth = false;
467 options.stencil = false;
468 options.antialias = false;
469 options.preserveDrawingBuffer = true;
470 options.failIfMajorPerformanceCaveat = false;
472 const bool resistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
473 "Fallback", RFPTarget::WebGLRenderCapability);
474 const auto initDesc = webgl::InitContextDesc{
475 .isWebgl2 = true,
476 .resistFingerprinting = resistFingerprinting,
477 .principalKey = 0,
478 .size = {1, 1},
479 .options = options,
482 webgl::InitContextResult initResult;
483 mWebgl = WebGLContext::Create(nullptr, initDesc, &initResult);
484 if (!mWebgl) {
485 // There was a non-recoverable error when trying to create a host context.
486 sContextInitError = true;
487 mWebgl = nullptr;
488 return false;
490 if (mWebgl->IsContextLost()) {
491 mWebgl = nullptr;
492 return false;
495 mMaxTextureSize = initResult.limits.maxTex2dSize;
497 if (kIsMacOS) {
498 mRasterizationTruncates = initResult.vendor == gl::GLVendor::ATI;
501 CachePrefs();
503 if (!CreateShaders()) {
504 // There was a non-recoverable error when trying to init shaders.
505 sContextInitError = true;
506 mWebgl = nullptr;
507 return false;
510 mWGRPathBuilder = WGR::wgr_new_builder();
512 return true;
515 inline void SharedContextWebgl::BlendFunc(GLenum aSrcFactor,
516 GLenum aDstFactor) {
517 mWebgl->BlendFuncSeparate({}, aSrcFactor, aDstFactor, aSrcFactor, aDstFactor);
520 void SharedContextWebgl::SetBlendState(CompositionOp aOp,
521 const Maybe<DeviceColor>& aColor) {
522 if (aOp == mLastCompositionOp && mLastBlendColor == aColor) {
523 return;
525 mLastCompositionOp = aOp;
526 mLastBlendColor = aColor;
527 // AA is not supported for all composition ops, so switching blend modes may
528 // cause a toggle in AA state. Certain ops such as OP_SOURCE require output
529 // alpha that is blended separately from AA coverage. This would require two
530 // stage blending which can incur a substantial performance penalty, so to
531 // work around this currently we just disable AA for those ops.
533 // Map the composition op to a WebGL blend mode, if possible.
534 bool enabled = true;
535 switch (aOp) {
536 case CompositionOp::OP_OVER:
537 if (aColor) {
538 // If a color is supplied, then we blend subpixel text.
539 mWebgl->BlendColor(aColor->b, aColor->g, aColor->r, 1.0f);
540 BlendFunc(LOCAL_GL_CONSTANT_COLOR, LOCAL_GL_ONE_MINUS_SRC_COLOR);
541 } else {
542 BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
544 break;
545 case CompositionOp::OP_ADD:
546 BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE);
547 break;
548 case CompositionOp::OP_ATOP:
549 BlendFunc(LOCAL_GL_DST_ALPHA, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
550 break;
551 case CompositionOp::OP_SOURCE:
552 if (aColor) {
553 // If a color is supplied, then we assume there is clipping or AA. This
554 // requires that we still use an over blend func with the clip/AA alpha,
555 // while filling the interior with the unaltered color. Normally this
556 // would require dual source blending, but we can emulate it with only
557 // a blend color.
558 mWebgl->BlendColor(aColor->b, aColor->g, aColor->r, aColor->a);
559 BlendFunc(LOCAL_GL_CONSTANT_COLOR, LOCAL_GL_ONE_MINUS_SRC_COLOR);
560 } else {
561 enabled = false;
563 break;
564 case CompositionOp::OP_CLEAR:
565 // Assume the source is an alpha mask for clearing. Be careful to blend in
566 // the correct alpha if the target is opaque.
567 mWebgl->BlendFuncSeparate(
568 {}, LOCAL_GL_ZERO, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
569 IsOpaque(mCurrentTarget->GetFormat()) ? LOCAL_GL_ONE : LOCAL_GL_ZERO,
570 LOCAL_GL_ONE_MINUS_SRC_ALPHA);
571 break;
572 default:
573 enabled = false;
574 break;
577 mWebgl->SetEnabled(LOCAL_GL_BLEND, {}, enabled);
580 // Ensure the WebGL framebuffer is set to the current target.
581 bool SharedContextWebgl::SetTarget(DrawTargetWebgl* aDT) {
582 if (!mWebgl || mWebgl->IsContextLost()) {
583 return false;
585 if (aDT != mCurrentTarget) {
586 mCurrentTarget = aDT;
587 if (aDT) {
588 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, aDT->mFramebuffer);
589 mViewportSize = aDT->GetSize();
590 mWebgl->Viewport(0, 0, mViewportSize.width, mViewportSize.height);
593 return true;
596 // Replace the current clip rect with a new potentially-AA'd clip rect.
597 void SharedContextWebgl::SetClipRect(const Rect& aClipRect) {
598 // Only invalidate the clip rect if it actually changes.
599 if (!mClipAARect.IsEqualEdges(aClipRect)) {
600 mClipAARect = aClipRect;
601 // Store the integer-aligned bounds.
602 mClipRect = RoundedOut(aClipRect);
606 bool SharedContextWebgl::SetClipMask(const RefPtr<WebGLTexture>& aTex) {
607 if (mLastClipMask != aTex) {
608 if (!mWebgl) {
609 return false;
611 mWebgl->ActiveTexture(1);
612 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, aTex);
613 mWebgl->ActiveTexture(0);
614 mLastClipMask = aTex;
616 return true;
619 bool SharedContextWebgl::SetNoClipMask() {
620 if (mNoClipMask) {
621 return SetClipMask(mNoClipMask);
623 if (!mWebgl) {
624 return false;
626 mNoClipMask = mWebgl->CreateTexture();
627 if (!mNoClipMask) {
628 return false;
630 mWebgl->ActiveTexture(1);
631 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mNoClipMask);
632 static const auto solidMask =
633 std::array<const uint8_t, 4>{0xFF, 0xFF, 0xFF, 0xFF};
634 mWebgl->TexImage(0, LOCAL_GL_RGBA8, {0, 0, 0},
635 {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE},
636 {LOCAL_GL_TEXTURE_2D,
637 {1, 1, 1},
638 gfxAlphaType::NonPremult,
639 Some(Span{solidMask})});
640 InitTexParameters(mNoClipMask, false);
641 mWebgl->ActiveTexture(0);
642 mLastClipMask = mNoClipMask;
643 return true;
646 inline bool DrawTargetWebgl::ClipStack::operator==(
647 const DrawTargetWebgl::ClipStack& aOther) const {
648 // Verify the transform and bounds match.
649 if (!mTransform.FuzzyEquals(aOther.mTransform) ||
650 !mRect.IsEqualInterior(aOther.mRect)) {
651 return false;
653 // Verify the paths match.
654 if (!mPath) {
655 return !aOther.mPath;
657 if (!aOther.mPath ||
658 mPath->GetBackendType() != aOther.mPath->GetBackendType()) {
659 return false;
661 if (mPath->GetBackendType() != BackendType::SKIA) {
662 return mPath == aOther.mPath;
664 return static_cast<const PathSkia*>(mPath.get())->GetPath() ==
665 static_cast<const PathSkia*>(aOther.mPath.get())->GetPath();
668 // If the clip region can't be approximated by a simple clip rect, then we need
669 // to generate a clip mask that can represent the clip region per-pixel. We
670 // render to the Skia target temporarily, transparent outside the clip region,
671 // opaque inside, and upload this to a texture that can be used by the shaders.
672 bool DrawTargetWebgl::GenerateComplexClipMask() {
673 if (!mClipChanged || (mClipMask && mCachedClipStack == mClipStack)) {
674 mClipChanged = false;
675 // If the clip mask was already generated, use the cached mask and bounds.
676 mSharedContext->SetClipMask(mClipMask);
677 mSharedContext->SetClipRect(mClipBounds);
678 return true;
680 if (!mWebglValid) {
681 // If the Skia target is currently being used, then we can't render the mask
682 // in it.
683 return false;
685 RefPtr<WebGLContext> webgl = mSharedContext->mWebgl;
686 if (!webgl) {
687 return false;
689 bool init = false;
690 if (!mClipMask) {
691 mClipMask = webgl->CreateTexture();
692 if (!mClipMask) {
693 return false;
695 init = true;
697 // Try to get the bounds of the clip to limit the size of the mask.
698 if (Maybe<IntRect> clip = mSkia->GetDeviceClipRect(true)) {
699 mClipBounds = *clip;
700 } else {
701 // If we can't get bounds, then just use the entire viewport.
702 mClipBounds = GetRect();
704 mClipAARect = Rect(mClipBounds);
705 // If initializing the clip mask, then allocate the entire texture to ensure
706 // all pixels get filled with an empty mask regardless. Otherwise, restrict
707 // uploading to only the clip region.
708 RefPtr<DrawTargetSkia> dt = new DrawTargetSkia;
709 if (!dt->Init(mClipBounds.Size(), SurfaceFormat::A8)) {
710 return false;
712 // Set the clip region and fill the entire inside of it
713 // with opaque white.
714 mCachedClipStack.clear();
715 for (auto& clipStack : mClipStack) {
716 // Record the current state of the clip stack for this mask.
717 mCachedClipStack.push_back(clipStack);
718 dt->SetTransform(
719 Matrix(clipStack.mTransform).PostTranslate(-mClipBounds.TopLeft()));
720 if (clipStack.mPath) {
721 dt->PushClip(clipStack.mPath);
722 } else {
723 dt->PushClipRect(clipStack.mRect);
726 dt->SetTransform(Matrix::Translation(-mClipBounds.TopLeft()));
727 dt->FillRect(Rect(mClipBounds), ColorPattern(DeviceColor(1, 1, 1, 1)));
728 // Bind the clip mask for uploading.
729 webgl->ActiveTexture(1);
730 webgl->BindTexture(LOCAL_GL_TEXTURE_2D, mClipMask);
731 if (init) {
732 mSharedContext->InitTexParameters(mClipMask, false);
734 RefPtr<DataSourceSurface> data;
735 if (RefPtr<SourceSurface> snapshot = dt->Snapshot()) {
736 data = snapshot->GetDataSurface();
738 // Finally, upload the texture data and initialize texture storage if
739 // necessary.
740 if (init && mClipBounds.Size() != mSize) {
741 mSharedContext->UploadSurface(nullptr, SurfaceFormat::A8, GetRect(),
742 IntPoint(), true, true);
743 init = false;
745 mSharedContext->UploadSurface(data, SurfaceFormat::A8,
746 IntRect(IntPoint(), mClipBounds.Size()),
747 mClipBounds.TopLeft(), init);
748 webgl->ActiveTexture(0);
749 // We already bound the texture, so notify the shared context that the clip
750 // mask changed to it.
751 mSharedContext->mLastClipMask = mClipMask;
752 mSharedContext->SetClipRect(mClipBounds);
753 // We uploaded a surface, just as if we missed the texture cache, so account
754 // for that here.
755 mProfile.OnCacheMiss();
756 return !!data;
759 bool DrawTargetWebgl::SetSimpleClipRect() {
760 // Determine whether the clipping rectangle is simple enough to accelerate.
761 // Check if there is a device space clip rectangle available from the Skia
762 // target.
763 if (Maybe<IntRect> clip = mSkia->GetDeviceClipRect(false)) {
764 // If the clip is empty, leave the final integer clip rectangle empty to
765 // trivially discard the draw request.
766 // If the clip rect is larger than the viewport, just set it to the
767 // viewport.
768 if (!clip->IsEmpty() && clip->Contains(GetRect())) {
769 clip = Some(GetRect());
771 mSharedContext->SetClipRect(*clip);
772 mSharedContext->SetNoClipMask();
773 return true;
776 // There was no pixel-aligned clip rect available, so check the clip stack to
777 // see if there is an AA'd axis-aligned rectangle clip.
778 Rect rect(GetRect());
779 for (auto& clipStack : mClipStack) {
780 // If clip is a path or it has a non-axis-aligned transform, then it is
781 // complex.
782 if (clipStack.mPath ||
783 !clipStack.mTransform.PreservesAxisAlignedRectangles()) {
784 return false;
786 // Transform the rect and intersect it with the current clip.
787 rect =
788 clipStack.mTransform.TransformBounds(clipStack.mRect).Intersect(rect);
790 mSharedContext->SetClipRect(rect);
791 mSharedContext->SetNoClipMask();
792 return true;
795 // Installs the Skia clip rectangle, if applicable, onto the shared WebGL
796 // context as well as sets the WebGL framebuffer to the current target.
797 bool DrawTargetWebgl::PrepareContext(bool aClipped) {
798 if (!aClipped) {
799 // If no clipping requested, just set the clip rect to the viewport.
800 mSharedContext->SetClipRect(GetRect());
801 mSharedContext->SetNoClipMask();
802 // Ensure the clip gets reset if clipping is later requested for the target.
803 mRefreshClipState = true;
804 } else if (mRefreshClipState || !mSharedContext->IsCurrentTarget(this)) {
805 // Try to use a simple clip rect if possible. Otherwise, fall back to
806 // generating a clip mask texture that can represent complex clip regions.
807 if (!SetSimpleClipRect() && !GenerateComplexClipMask()) {
808 return false;
810 mClipChanged = false;
811 mRefreshClipState = false;
813 return mSharedContext->SetTarget(this);
816 bool SharedContextWebgl::IsContextLost() const {
817 return !mWebgl || mWebgl->IsContextLost();
820 // Signal to CanvasRenderingContext2D when the WebGL context is lost.
821 bool DrawTargetWebgl::IsValid() const {
822 return mSharedContext && !mSharedContext->IsContextLost();
825 bool DrawTargetWebgl::CanCreate(const IntSize& aSize, SurfaceFormat aFormat) {
826 if (!gfxVars::UseAcceleratedCanvas2D()) {
827 return false;
830 if (!Factory::AllowedSurfaceSize(aSize)) {
831 return false;
834 // The interpretation of the min-size and max-size follows from the old
835 // SkiaGL prefs. First just ensure that the context is not unreasonably
836 // small.
837 static const int32_t kMinDimension = 16;
838 if (std::min(aSize.width, aSize.height) < kMinDimension) {
839 return false;
842 int32_t minSize = StaticPrefs::gfx_canvas_accelerated_min_size();
843 if (aSize.width * aSize.height < minSize * minSize) {
844 return false;
847 // Maximum pref allows 3 different options:
848 // 0 means unlimited size,
849 // > 0 means use value as an absolute threshold,
850 // < 0 means use the number of screen pixels as a threshold.
851 int32_t maxSize = StaticPrefs::gfx_canvas_accelerated_max_size();
852 if (maxSize > 0) {
853 if (std::max(aSize.width, aSize.height) > maxSize) {
854 return false;
856 } else if (maxSize < 0) {
857 // Default to historical mobile screen size of 980x480, like FishIEtank.
858 // In addition, allow acceleration up to this size even if the screen is
859 // smaller. A lot content expects this size to work well. See Bug 999841
860 static const int32_t kScreenPixels = 980 * 480;
862 if (RefPtr<widget::Screen> screen =
863 widget::ScreenManager::GetSingleton().GetPrimaryScreen()) {
864 LayoutDeviceIntSize screenSize = screen->GetRect().Size();
865 if (aSize.width * aSize.height >
866 std::max(screenSize.width * screenSize.height, kScreenPixels)) {
867 return false;
872 return true;
875 already_AddRefed<DrawTargetWebgl> DrawTargetWebgl::Create(
876 const IntSize& aSize, SurfaceFormat aFormat,
877 const RefPtr<SharedContextWebgl>& aSharedContext) {
878 // Validate the size and format.
879 if (!CanCreate(aSize, aFormat)) {
880 return nullptr;
883 RefPtr<DrawTargetWebgl> dt = new DrawTargetWebgl;
884 if (!dt->Init(aSize, aFormat, aSharedContext) || !dt->IsValid()) {
885 return nullptr;
888 return dt.forget();
891 void* DrawTargetWebgl::GetNativeSurface(NativeSurfaceType aType) {
892 switch (aType) {
893 case NativeSurfaceType::WEBGL_CONTEXT:
894 // If the context is lost, then don't attempt to access it.
895 if (mSharedContext->IsContextLost()) {
896 return nullptr;
898 if (!mWebglValid) {
899 FlushFromSkia();
901 return mSharedContext->mWebgl.get();
902 default:
903 return nullptr;
907 // Wrap a WebGL texture holding a snapshot with a texture handle. Note that
908 // while the texture is still in use as the backing texture of a framebuffer,
909 // it's texture memory is not currently tracked with other texture handles.
910 // Once it is finally orphaned and used as a texture handle, it must be added
911 // to the resource usage totals.
912 already_AddRefed<TextureHandle> SharedContextWebgl::WrapSnapshot(
913 const IntSize& aSize, SurfaceFormat aFormat, RefPtr<WebGLTexture> aTex) {
914 // Ensure there is enough space for the texture.
915 size_t usedBytes = BackingTexture::UsedBytes(aFormat, aSize);
916 PruneTextureMemory(usedBytes, false);
917 // Allocate a handle for the texture
918 RefPtr<StandaloneTexture> handle =
919 new StandaloneTexture(aSize, aFormat, aTex.forget());
920 mStandaloneTextures.push_back(handle);
921 mTextureHandles.insertFront(handle);
922 mTotalTextureMemory += usedBytes;
923 mUsedTextureMemory += usedBytes;
924 ++mNumTextureHandles;
925 return handle.forget();
928 void SharedContextWebgl::SetTexFilter(WebGLTexture* aTex, bool aFilter) {
929 mWebgl->TexParameter_base(
930 LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
931 FloatOrInt(aFilter ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST));
932 mWebgl->TexParameter_base(
933 LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
934 FloatOrInt(aFilter ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST));
937 void SharedContextWebgl::InitTexParameters(WebGLTexture* aTex, bool aFilter) {
938 mWebgl->TexParameter_base(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
939 FloatOrInt(LOCAL_GL_CLAMP_TO_EDGE));
940 mWebgl->TexParameter_base(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
941 FloatOrInt(LOCAL_GL_CLAMP_TO_EDGE));
942 SetTexFilter(aTex, aFilter);
945 // Copy the contents of the WebGL framebuffer into a WebGL texture.
946 already_AddRefed<TextureHandle> SharedContextWebgl::CopySnapshot(
947 const IntRect& aRect, TextureHandle* aHandle) {
948 if (!mWebgl || mWebgl->IsContextLost()) {
949 return nullptr;
952 // If the target is going away, then we can just directly reuse the
953 // framebuffer texture since it will never change.
954 RefPtr<WebGLTexture> tex = mWebgl->CreateTexture();
955 if (!tex) {
956 return nullptr;
959 // If copying from a non-DT source, we have to bind a scratch framebuffer for
960 // reading.
961 if (aHandle) {
962 if (!mScratchFramebuffer) {
963 mScratchFramebuffer = mWebgl->CreateFramebuffer();
965 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mScratchFramebuffer);
967 webgl::FbAttachInfo attachInfo;
968 attachInfo.tex = aHandle->GetBackingTexture()->GetWebGLTexture();
969 mWebgl->FramebufferAttach(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
970 LOCAL_GL_TEXTURE_2D, attachInfo);
973 // Create a texture to hold the copy
974 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex);
975 mWebgl->TexStorage(LOCAL_GL_TEXTURE_2D, 1, LOCAL_GL_RGBA8,
976 {uint32_t(aRect.width), uint32_t(aRect.height), 1});
977 InitTexParameters(tex);
978 // Copy the framebuffer into the texture
979 mWebgl->CopyTexImage(LOCAL_GL_TEXTURE_2D, 0, 0, {0, 0, 0}, {aRect.x, aRect.y},
980 {uint32_t(aRect.width), uint32_t(aRect.height)});
981 ClearLastTexture();
983 SurfaceFormat format =
984 aHandle ? aHandle->GetFormat() : mCurrentTarget->GetFormat();
985 already_AddRefed<TextureHandle> result =
986 WrapSnapshot(aRect.Size(), format, tex.forget());
988 // Restore the actual framebuffer after reading is done.
989 if (aHandle && mCurrentTarget) {
990 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mCurrentTarget->mFramebuffer);
993 return result;
996 inline DrawTargetWebgl::AutoRestoreContext::AutoRestoreContext(
997 DrawTargetWebgl* aTarget)
998 : mTarget(aTarget),
999 mClipAARect(aTarget->mSharedContext->mClipAARect),
1000 mLastClipMask(aTarget->mSharedContext->mLastClipMask) {}
1002 inline DrawTargetWebgl::AutoRestoreContext::~AutoRestoreContext() {
1003 mTarget->mSharedContext->SetClipRect(mClipAARect);
1004 if (mLastClipMask) {
1005 mTarget->mSharedContext->SetClipMask(mLastClipMask);
1007 mTarget->mRefreshClipState = true;
1010 // Utility method to install the target before copying a snapshot.
1011 already_AddRefed<TextureHandle> DrawTargetWebgl::CopySnapshot(
1012 const IntRect& aRect) {
1013 AutoRestoreContext restore(this);
1014 if (!PrepareContext(false)) {
1015 return nullptr;
1017 return mSharedContext->CopySnapshot(aRect);
1020 bool DrawTargetWebgl::HasDataSnapshot() const {
1021 return (mSkiaValid && !mSkiaLayer) || (mSnapshot && mSnapshot->HasReadData());
1024 bool DrawTargetWebgl::PrepareSkia() {
1025 if (!mSkiaValid) {
1026 ReadIntoSkia();
1027 } else if (mSkiaLayer) {
1028 FlattenSkia();
1030 return mSkiaValid;
1033 bool DrawTargetWebgl::EnsureDataSnapshot() {
1034 return HasDataSnapshot() || PrepareSkia();
1037 void DrawTargetWebgl::PrepareShmem() { PrepareSkia(); }
1039 // Borrow a snapshot that may be used by another thread for composition. Only
1040 // Skia snapshots are safe to pass around.
1041 already_AddRefed<SourceSurface> DrawTargetWebgl::GetDataSnapshot() {
1042 PrepareSkia();
1043 return mSkia->Snapshot(mFormat);
1046 already_AddRefed<SourceSurface> DrawTargetWebgl::Snapshot() {
1047 // If already using the Skia fallback, then just snapshot that.
1048 if (mSkiaValid) {
1049 return GetDataSnapshot();
1052 // There's no valid Skia snapshot, so we need to get one from the WebGL
1053 // context.
1054 if (!mSnapshot) {
1055 // Create a copy-on-write reference to this target.
1056 mSnapshot = new SourceSurfaceWebgl(this);
1058 return do_AddRef(mSnapshot);
1061 // If we need to provide a snapshot for another DrawTargetWebgl that shares the
1062 // same WebGL context, then it is safe to directly return a snapshot. Otherwise,
1063 // we may be exporting to another thread and require a data snapshot.
1064 already_AddRefed<SourceSurface> DrawTargetWebgl::GetOptimizedSnapshot(
1065 DrawTarget* aTarget) {
1066 if (aTarget && aTarget->GetBackendType() == BackendType::WEBGL &&
1067 static_cast<DrawTargetWebgl*>(aTarget)->mSharedContext ==
1068 mSharedContext) {
1069 return Snapshot();
1071 return GetDataSnapshot();
1074 // Read from the WebGL context into a buffer. This handles both swizzling BGRA
1075 // to RGBA and flipping the image.
1076 bool SharedContextWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride,
1077 SurfaceFormat aFormat, const IntRect& aBounds,
1078 TextureHandle* aHandle) {
1079 MOZ_ASSERT(aFormat == SurfaceFormat::B8G8R8A8 ||
1080 aFormat == SurfaceFormat::B8G8R8X8);
1082 // If reading into a new texture, we have to bind it to a scratch framebuffer
1083 // for reading.
1084 if (aHandle) {
1085 if (!mScratchFramebuffer) {
1086 mScratchFramebuffer = mWebgl->CreateFramebuffer();
1088 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mScratchFramebuffer);
1089 webgl::FbAttachInfo attachInfo;
1090 attachInfo.tex = aHandle->GetBackingTexture()->GetWebGLTexture();
1091 mWebgl->FramebufferAttach(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
1092 LOCAL_GL_TEXTURE_2D, attachInfo);
1093 } else if (mCurrentTarget && mCurrentTarget->mIsClear) {
1094 // If reading from a target that is still clear, then avoid the readback by
1095 // just clearing the data.
1096 SkPixmap(MakeSkiaImageInfo(aBounds.Size(), aFormat), aDstData, aDstStride)
1097 .erase(IsOpaque(aFormat) ? SK_ColorBLACK : SK_ColorTRANSPARENT);
1098 return true;
1101 webgl::ReadPixelsDesc desc;
1102 desc.srcOffset = *ivec2::From(aBounds);
1103 desc.size = *uvec2::FromSize(aBounds);
1104 desc.packState.rowLength = aDstStride / 4;
1105 Range<uint8_t> range = {aDstData, size_t(aDstStride) * aBounds.height};
1106 mWebgl->ReadPixelsInto(desc, range);
1108 // Restore the actual framebuffer after reading is done.
1109 if (aHandle && mCurrentTarget) {
1110 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mCurrentTarget->mFramebuffer);
1113 return true;
1116 already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshot(
1117 TextureHandle* aHandle) {
1118 // Allocate a data surface, map it, and read from the WebGL context into the
1119 // surface.
1120 SurfaceFormat format = SurfaceFormat::UNKNOWN;
1121 IntRect bounds;
1122 if (aHandle) {
1123 format = aHandle->GetFormat();
1124 bounds = aHandle->GetBounds();
1125 } else {
1126 format = mCurrentTarget->GetFormat();
1127 bounds = mCurrentTarget->GetRect();
1129 RefPtr<DataSourceSurface> surface =
1130 Factory::CreateDataSourceSurface(bounds.Size(), format);
1131 if (!surface) {
1132 return nullptr;
1134 DataSourceSurface::ScopedMap dstMap(surface, DataSourceSurface::WRITE);
1135 if (!dstMap.IsMapped() || !ReadInto(dstMap.GetData(), dstMap.GetStride(),
1136 format, bounds, aHandle)) {
1137 return nullptr;
1139 return surface.forget();
1142 // Utility method to install the target before reading a snapshot.
1143 bool DrawTargetWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride) {
1144 if (!PrepareContext(false)) {
1145 return false;
1148 return mSharedContext->ReadInto(aDstData, aDstStride, GetFormat(), GetRect());
1151 // Utility method to install the target before reading a snapshot.
1152 already_AddRefed<DataSourceSurface> DrawTargetWebgl::ReadSnapshot() {
1153 AutoRestoreContext restore(this);
1154 if (!PrepareContext(false)) {
1155 return nullptr;
1157 mProfile.OnReadback();
1158 return mSharedContext->ReadSnapshot();
1161 already_AddRefed<SourceSurface> DrawTargetWebgl::GetBackingSurface() {
1162 return Snapshot();
1165 void DrawTargetWebgl::DetachAllSnapshots() {
1166 mSkia->DetachAllSnapshots();
1167 ClearSnapshot();
1170 // Prepare the framebuffer for accelerated drawing. Any cached snapshots will
1171 // be invalidated if not detached and copied here. Ensure the WebGL
1172 // framebuffer's contents are updated if still somehow stored in the Skia
1173 // framebuffer.
1174 bool DrawTargetWebgl::MarkChanged() {
1175 if (mSnapshot) {
1176 // Try to copy the target into a new texture if possible.
1177 ClearSnapshot(true, true);
1179 if (!mWebglValid && !FlushFromSkia()) {
1180 return false;
1182 mSkiaValid = false;
1183 mIsClear = false;
1184 return true;
1187 void DrawTargetWebgl::MarkSkiaChanged(bool aOverwrite) {
1188 if (aOverwrite) {
1189 mSkiaValid = true;
1190 mSkiaLayer = false;
1191 } else if (!mSkiaValid) {
1192 if (ReadIntoSkia()) {
1193 // Signal that we've hit a complete software fallback.
1194 mProfile.OnFallback();
1196 } else if (mSkiaLayer) {
1197 FlattenSkia();
1199 mWebglValid = false;
1200 mIsClear = false;
1203 // Whether a given composition operator is associative and thus allows drawing
1204 // into a separate layer that can be later composited back into the WebGL
1205 // context.
1206 static inline bool SupportsLayering(const DrawOptions& aOptions) {
1207 switch (aOptions.mCompositionOp) {
1208 case CompositionOp::OP_OVER:
1209 // Layering is only supported for the default source-over composition op.
1210 return true;
1211 default:
1212 return false;
1216 void DrawTargetWebgl::MarkSkiaChanged(const DrawOptions& aOptions) {
1217 if (SupportsLayering(aOptions)) {
1218 if (!mSkiaValid) {
1219 // If the Skia context needs initialization, clear it and enable layering.
1220 mSkiaValid = true;
1221 if (mWebglValid) {
1222 mProfile.OnLayer();
1223 mSkiaLayer = true;
1224 mSkiaLayerClear = mIsClear;
1225 mSkia->DetachAllSnapshots();
1226 if (mSkiaLayerClear) {
1227 // Avoid blending later by making sure the layer background is filled
1228 // with opaque alpha values if necessary.
1229 mSkiaNoClip->FillRect(Rect(mSkiaNoClip->GetRect()), GetClearPattern(),
1230 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
1231 } else {
1232 mSkiaNoClip->ClearRect(Rect(mSkiaNoClip->GetRect()));
1236 // The WebGL context is no longer up-to-date.
1237 mWebglValid = false;
1238 mIsClear = false;
1239 } else {
1240 // For other composition ops, just overwrite the Skia data.
1241 MarkSkiaChanged();
1245 bool DrawTargetWebgl::LockBits(uint8_t** aData, IntSize* aSize,
1246 int32_t* aStride, SurfaceFormat* aFormat,
1247 IntPoint* aOrigin) {
1248 // Can only access pixels if there is valid, flattened Skia data.
1249 if (mSkiaValid && !mSkiaLayer) {
1250 MarkSkiaChanged();
1251 return mSkia->LockBits(aData, aSize, aStride, aFormat, aOrigin);
1253 return false;
1256 void DrawTargetWebgl::ReleaseBits(uint8_t* aData) {
1257 // Can only access pixels if there is valid, flattened Skia data.
1258 if (mSkiaValid && !mSkiaLayer) {
1259 mSkia->ReleaseBits(aData);
1263 // Format is x, y, alpha
1264 static const float kRectVertexData[12] = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
1265 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f};
1267 // Orphans the contents of the path vertex buffer. The beginning of the buffer
1268 // always contains data for a simple rectangle draw to avoid needing to switch
1269 // buffers.
1270 void SharedContextWebgl::ResetPathVertexBuffer(bool aChanged) {
1271 mWebgl->BindBuffer(LOCAL_GL_ARRAY_BUFFER, mPathVertexBuffer.get());
1272 mWebgl->UninitializedBufferData_SizeOnly(
1273 LOCAL_GL_ARRAY_BUFFER,
1274 std::max(size_t(mPathVertexCapacity), sizeof(kRectVertexData)),
1275 LOCAL_GL_DYNAMIC_DRAW);
1276 mWebgl->BufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, sizeof(kRectVertexData),
1277 (const uint8_t*)kRectVertexData);
1278 mPathVertexOffset = sizeof(kRectVertexData);
1279 if (aChanged) {
1280 mWGROutputBuffer.reset(
1281 mPathVertexCapacity > 0
1282 ? new (fallible) WGR::OutputVertex[mPathVertexCapacity /
1283 sizeof(WGR::OutputVertex)]
1284 : nullptr);
1288 // Attempts to create all shaders and resources to be used for drawing commands.
1289 // Returns whether or not this succeeded.
1290 bool SharedContextWebgl::CreateShaders() {
1291 if (!mPathVertexArray) {
1292 mPathVertexArray = mWebgl->CreateVertexArray();
1294 if (!mPathVertexBuffer) {
1295 mPathVertexBuffer = mWebgl->CreateBuffer();
1296 mWebgl->BindVertexArray(mPathVertexArray.get());
1297 ResetPathVertexBuffer();
1298 mWebgl->EnableVertexAttribArray(0);
1300 webgl::VertAttribPointerDesc attribDesc;
1301 attribDesc.channels = 3;
1302 attribDesc.type = LOCAL_GL_FLOAT;
1303 attribDesc.normalized = false;
1304 mWebgl->VertexAttribPointer(0, attribDesc);
1306 if (!mSolidProgram) {
1307 // AA is computed by using the basis vectors of the transform to determine
1308 // both the scale and orientation. The scale is then used to extrude the
1309 // rectangle outward by 1 screen-space pixel to account for the AA region.
1310 // The distance to the rectangle edges is passed to the fragment shader in
1311 // an interpolant, biased by 0.5 so it represents the desired coverage. The
1312 // minimum coverage is then chosen by the fragment shader to use as an AA
1313 // coverage value to modulate the color.
1314 auto vsSource =
1315 "attribute vec3 a_vertex;\n"
1316 "uniform vec2 u_transform[3];\n"
1317 "uniform vec2 u_viewport;\n"
1318 "uniform vec4 u_clipbounds;\n"
1319 "uniform float u_aa;\n"
1320 "varying vec2 v_cliptc;\n"
1321 "varying vec4 v_clipdist;\n"
1322 "varying vec4 v_dist;\n"
1323 "varying float v_alpha;\n"
1324 "void main() {\n"
1325 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1326 " dot(u_transform[1], u_transform[1]));\n"
1327 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1328 " scale *= invScale;\n"
1329 " vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
1330 "1.0);\n"
1331 " vec2 vertex = u_transform[0] * extrude.x +\n"
1332 " u_transform[1] * extrude.y +\n"
1333 " u_transform[2];\n"
1334 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1335 " v_cliptc = vertex / u_viewport;\n"
1336 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
1337 " u_clipbounds.zw - vertex);\n"
1338 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
1339 " v_alpha = a_vertex.z;\n"
1340 "}\n";
1341 auto fsSource =
1342 "precision mediump float;\n"
1343 "uniform vec4 u_color;\n"
1344 "uniform sampler2D u_clipmask;\n"
1345 "varying highp vec2 v_cliptc;\n"
1346 "varying vec4 v_clipdist;\n"
1347 "varying vec4 v_dist;\n"
1348 "varying float v_alpha;\n"
1349 "void main() {\n"
1350 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1351 " vec4 dist = min(v_dist, v_clipdist);\n"
1352 " dist.xy = min(dist.xy, dist.zw);\n"
1353 " float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
1354 " gl_FragColor = clip * aa * u_color;\n"
1355 "}\n";
1356 RefPtr<WebGLShader> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER);
1357 mWebgl->ShaderSource(*vsId, vsSource);
1358 mWebgl->CompileShader(*vsId);
1359 if (!mWebgl->GetCompileResult(*vsId).success) {
1360 return false;
1362 RefPtr<WebGLShader> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER);
1363 mWebgl->ShaderSource(*fsId, fsSource);
1364 mWebgl->CompileShader(*fsId);
1365 if (!mWebgl->GetCompileResult(*fsId).success) {
1366 return false;
1368 mSolidProgram = mWebgl->CreateProgram();
1369 mWebgl->AttachShader(*mSolidProgram, *vsId);
1370 mWebgl->AttachShader(*mSolidProgram, *fsId);
1371 mWebgl->BindAttribLocation(*mSolidProgram, 0, "a_vertex");
1372 mWebgl->LinkProgram(*mSolidProgram);
1373 if (!mWebgl->GetLinkResult(*mSolidProgram).success) {
1374 return false;
1376 mSolidProgramViewport = GetUniformLocation(mSolidProgram, "u_viewport");
1377 mSolidProgramAA = GetUniformLocation(mSolidProgram, "u_aa");
1378 mSolidProgramTransform = GetUniformLocation(mSolidProgram, "u_transform");
1379 mSolidProgramColor = GetUniformLocation(mSolidProgram, "u_color");
1380 mSolidProgramClipMask = GetUniformLocation(mSolidProgram, "u_clipmask");
1381 mSolidProgramClipBounds = GetUniformLocation(mSolidProgram, "u_clipbounds");
1382 if (!mSolidProgramViewport || !mSolidProgramAA || !mSolidProgramTransform ||
1383 !mSolidProgramColor || !mSolidProgramClipMask ||
1384 !mSolidProgramClipBounds) {
1385 return false;
1387 mWebgl->UseProgram(mSolidProgram);
1388 UniformData(LOCAL_GL_INT, mSolidProgramClipMask, Array<int32_t, 1>{1});
1391 if (!mImageProgram) {
1392 auto vsSource =
1393 "attribute vec3 a_vertex;\n"
1394 "uniform vec2 u_viewport;\n"
1395 "uniform vec4 u_clipbounds;\n"
1396 "uniform float u_aa;\n"
1397 "uniform vec2 u_transform[3];\n"
1398 "uniform vec2 u_texmatrix[3];\n"
1399 "varying vec2 v_cliptc;\n"
1400 "varying vec2 v_texcoord;\n"
1401 "varying vec4 v_clipdist;\n"
1402 "varying vec4 v_dist;\n"
1403 "varying float v_alpha;\n"
1404 "void main() {\n"
1405 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1406 " dot(u_transform[1], u_transform[1]));\n"
1407 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1408 " scale *= invScale;\n"
1409 " vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
1410 "1.0);\n"
1411 " vec2 vertex = u_transform[0] * extrude.x +\n"
1412 " u_transform[1] * extrude.y +\n"
1413 " u_transform[2];\n"
1414 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1415 " v_cliptc = vertex / u_viewport;\n"
1416 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
1417 " u_clipbounds.zw - vertex);\n"
1418 " v_texcoord = u_texmatrix[0] * extrude.x +\n"
1419 " u_texmatrix[1] * extrude.y +\n"
1420 " u_texmatrix[2];\n"
1421 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
1422 " v_alpha = a_vertex.z;\n"
1423 "}\n";
1424 auto fsSource =
1425 "precision mediump float;\n"
1426 "uniform vec4 u_texbounds;\n"
1427 "uniform vec4 u_color;\n"
1428 "uniform float u_swizzle;\n"
1429 "uniform sampler2D u_sampler;\n"
1430 "uniform sampler2D u_clipmask;\n"
1431 "varying highp vec2 v_cliptc;\n"
1432 "varying highp vec2 v_texcoord;\n"
1433 "varying vec4 v_clipdist;\n"
1434 "varying vec4 v_dist;\n"
1435 "varying float v_alpha;\n"
1436 "void main() {\n"
1437 " highp vec2 tc = clamp(v_texcoord, u_texbounds.xy,\n"
1438 " u_texbounds.zw);\n"
1439 " vec4 image = texture2D(u_sampler, tc);\n"
1440 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1441 " vec4 dist = min(v_dist, v_clipdist);\n"
1442 " dist.xy = min(dist.xy, dist.zw);\n"
1443 " float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
1444 " gl_FragColor = clip * aa * u_color *\n"
1445 " mix(image, image.rrrr, u_swizzle);\n"
1446 "}\n";
1447 RefPtr<WebGLShader> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER);
1448 mWebgl->ShaderSource(*vsId, vsSource);
1449 mWebgl->CompileShader(*vsId);
1450 if (!mWebgl->GetCompileResult(*vsId).success) {
1451 return false;
1453 RefPtr<WebGLShader> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER);
1454 mWebgl->ShaderSource(*fsId, fsSource);
1455 mWebgl->CompileShader(*fsId);
1456 if (!mWebgl->GetCompileResult(*fsId).success) {
1457 return false;
1459 mImageProgram = mWebgl->CreateProgram();
1460 mWebgl->AttachShader(*mImageProgram, *vsId);
1461 mWebgl->AttachShader(*mImageProgram, *fsId);
1462 mWebgl->BindAttribLocation(*mImageProgram, 0, "a_vertex");
1463 mWebgl->LinkProgram(*mImageProgram);
1464 if (!mWebgl->GetLinkResult(*mImageProgram).success) {
1465 return false;
1467 mImageProgramViewport = GetUniformLocation(mImageProgram, "u_viewport");
1468 mImageProgramAA = GetUniformLocation(mImageProgram, "u_aa");
1469 mImageProgramTransform = GetUniformLocation(mImageProgram, "u_transform");
1470 mImageProgramTexMatrix = GetUniformLocation(mImageProgram, "u_texmatrix");
1471 mImageProgramTexBounds = GetUniformLocation(mImageProgram, "u_texbounds");
1472 mImageProgramSwizzle = GetUniformLocation(mImageProgram, "u_swizzle");
1473 mImageProgramColor = GetUniformLocation(mImageProgram, "u_color");
1474 mImageProgramSampler = GetUniformLocation(mImageProgram, "u_sampler");
1475 mImageProgramClipMask = GetUniformLocation(mImageProgram, "u_clipmask");
1476 mImageProgramClipBounds = GetUniformLocation(mImageProgram, "u_clipbounds");
1477 if (!mImageProgramViewport || !mImageProgramAA || !mImageProgramTransform ||
1478 !mImageProgramTexMatrix || !mImageProgramTexBounds ||
1479 !mImageProgramSwizzle || !mImageProgramColor || !mImageProgramSampler ||
1480 !mImageProgramClipMask || !mImageProgramClipBounds) {
1481 return false;
1483 mWebgl->UseProgram(mImageProgram);
1484 UniformData(LOCAL_GL_INT, mImageProgramSampler, Array<int32_t, 1>{0});
1485 UniformData(LOCAL_GL_INT, mImageProgramClipMask, Array<int32_t, 1>{1});
1487 return true;
1490 void SharedContextWebgl::EnableScissor(const IntRect& aRect) {
1491 // Only update scissor state if it actually changes.
1492 if (!mLastScissor.IsEqualEdges(aRect)) {
1493 mLastScissor = aRect;
1494 mWebgl->Scissor(aRect.x, aRect.y, aRect.width, aRect.height);
1496 if (!mScissorEnabled) {
1497 mScissorEnabled = true;
1498 mWebgl->SetEnabled(LOCAL_GL_SCISSOR_TEST, {}, true);
1502 void SharedContextWebgl::DisableScissor() {
1503 if (mScissorEnabled) {
1504 mScissorEnabled = false;
1505 mWebgl->SetEnabled(LOCAL_GL_SCISSOR_TEST, {}, false);
1509 inline ColorPattern DrawTargetWebgl::GetClearPattern() const {
1510 return ColorPattern(
1511 DeviceColor(0.0f, 0.0f, 0.0f, IsOpaque(mFormat) ? 1.0f : 0.0f));
1514 // Check if the transformed rect would contain the entire viewport.
1515 inline bool DrawTargetWebgl::RectContainsViewport(const Rect& aRect) const {
1516 return mTransform.PreservesAxisAlignedRectangles() &&
1517 MatrixDouble(mTransform)
1518 .TransformBounds(
1519 RectDouble(aRect.x, aRect.y, aRect.width, aRect.height))
1520 .Contains(RectDouble(GetRect()));
1523 // Ensure that the rect, after transform, is within reasonable precision limits
1524 // such that when transformed and clipped in the shader it will not round bits
1525 // from the mantissa in a way that will diverge in a noticeable way from path
1526 // geometry calculated by the path fallback.
1527 static inline bool RectInsidePrecisionLimits(const Rect& aRect,
1528 const Matrix& aTransform) {
1529 return Rect(-(1 << 20), -(1 << 20), 2 << 20, 2 << 20)
1530 .Contains(aTransform.TransformBounds(aRect));
1533 void DrawTargetWebgl::ClearRect(const Rect& aRect) {
1534 if (mIsClear) {
1535 // No need to clear anything if the entire framebuffer is already clear.
1536 return;
1539 bool containsViewport = RectContainsViewport(aRect);
1540 if (containsViewport) {
1541 // If the rect encompasses the entire viewport, just clear the viewport
1542 // instead to avoid transform issues.
1543 DrawRect(Rect(GetRect()), GetClearPattern(),
1544 DrawOptions(1.0f, CompositionOp::OP_CLEAR), Nothing(), nullptr,
1545 false);
1546 } else if (RectInsidePrecisionLimits(aRect, mTransform)) {
1547 // If the rect transform won't stress precision, then just use it.
1548 DrawRect(aRect, GetClearPattern(),
1549 DrawOptions(1.0f, CompositionOp::OP_CLEAR));
1550 } else {
1551 // Otherwise, using the transform in the shader may lead to inaccuracies, so
1552 // just fall back.
1553 MarkSkiaChanged();
1554 mSkia->ClearRect(aRect);
1557 // If the clear rectangle encompasses the entire viewport and is not clipped,
1558 // then mark the target as entirely clear.
1559 if (containsViewport && mSharedContext->IsCurrentTarget(this) &&
1560 !mSharedContext->HasClipMask() &&
1561 mSharedContext->mClipAARect.Contains(Rect(GetRect()))) {
1562 mIsClear = true;
1566 static inline DeviceColor PremultiplyColor(const DeviceColor& aColor,
1567 float aAlpha = 1.0f) {
1568 float a = aColor.a * aAlpha;
1569 return DeviceColor(aColor.r * a, aColor.g * a, aColor.b * a, a);
1572 // Attempts to create the framebuffer used for drawing and also any relevant
1573 // non-shared resources. Returns whether or not this succeeded.
1574 bool DrawTargetWebgl::CreateFramebuffer() {
1575 RefPtr<WebGLContext> webgl = mSharedContext->mWebgl;
1576 if (!mFramebuffer) {
1577 mFramebuffer = webgl->CreateFramebuffer();
1579 if (!mTex) {
1580 mTex = webgl->CreateTexture();
1581 webgl->BindTexture(LOCAL_GL_TEXTURE_2D, mTex);
1582 webgl->TexStorage(LOCAL_GL_TEXTURE_2D, 1, LOCAL_GL_RGBA8,
1583 {uint32_t(mSize.width), uint32_t(mSize.height), 1});
1584 mSharedContext->InitTexParameters(mTex);
1585 webgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mFramebuffer);
1586 webgl::FbAttachInfo attachInfo;
1587 attachInfo.tex = mTex;
1588 webgl->FramebufferAttach(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
1589 LOCAL_GL_TEXTURE_2D, attachInfo);
1590 webgl->Viewport(0, 0, mSize.width, mSize.height);
1591 mSharedContext->DisableScissor();
1592 DeviceColor color = PremultiplyColor(GetClearPattern().mColor);
1593 webgl->ClearColor(color.b, color.g, color.r, color.a);
1594 webgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT);
1595 mSharedContext->ClearTarget();
1596 mSharedContext->ClearLastTexture();
1598 return true;
1601 void DrawTargetWebgl::CopySurface(SourceSurface* aSurface,
1602 const IntRect& aSourceRect,
1603 const IntPoint& aDestination) {
1604 // Intersect the source and destination rectangles with the viewport bounds.
1605 IntRect destRect =
1606 IntRect(aDestination, aSourceRect.Size()).SafeIntersect(GetRect());
1607 IntRect srcRect = destRect - aDestination + aSourceRect.TopLeft();
1608 if (srcRect.IsEmpty()) {
1609 return;
1612 if (mSkiaValid) {
1613 if (mSkiaLayer) {
1614 if (destRect.Contains(GetRect())) {
1615 // If the the destination would override the entire layer, discard the
1616 // layer.
1617 mSkiaLayer = false;
1618 } else if (!IsOpaque(aSurface->GetFormat())) {
1619 // If the surface is not opaque, copying it into the layer results in
1620 // unintended blending rather than a copy to the destination.
1621 FlattenSkia();
1623 } else {
1624 // If there is no layer, copying is safe.
1625 MarkSkiaChanged();
1627 mSkia->CopySurface(aSurface, srcRect, destRect.TopLeft());
1628 return;
1631 IntRect samplingRect;
1632 if (!mSharedContext->IsCompatibleSurface(aSurface)) {
1633 // If this data surface completely overwrites the framebuffer, then just
1634 // copy it to the Skia target.
1635 if (destRect.Contains(GetRect())) {
1636 MarkSkiaChanged(true);
1637 mSkia->DetachAllSnapshots();
1638 mSkiaNoClip->CopySurface(aSurface, srcRect, destRect.TopLeft());
1639 return;
1642 // CopySurface usually only samples a surface once, so don't cache the
1643 // entire surface as it is unlikely to be reused. Limit it to the used
1644 // source rectangle instead.
1645 IntRect surfaceRect = aSurface->GetRect();
1646 if (!srcRect.IsEqualEdges(surfaceRect)) {
1647 samplingRect = srcRect.SafeIntersect(surfaceRect);
1651 Matrix matrix = Matrix::Translation(destRect.TopLeft() - srcRect.TopLeft());
1652 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, matrix,
1653 SamplingFilter::POINT, samplingRect);
1654 DrawRect(Rect(destRect), pattern, DrawOptions(1.0f, CompositionOp::OP_SOURCE),
1655 Nothing(), nullptr, false, false);
1658 void DrawTargetWebgl::PushClip(const Path* aPath) {
1659 if (aPath && aPath->GetBackendType() == BackendType::SKIA) {
1660 // Detect if the path is really just a rect to simplify caching.
1661 const PathSkia* pathSkia = static_cast<const PathSkia*>(aPath);
1662 const SkPath& skPath = pathSkia->GetPath();
1663 SkRect rect = SkRect::MakeEmpty();
1664 if (skPath.isRect(&rect)) {
1665 PushClipRect(SkRectToRect(rect));
1666 return;
1670 mClipChanged = true;
1671 mRefreshClipState = true;
1672 mSkia->PushClip(aPath);
1674 mClipStack.push_back({GetTransform(), Rect(), aPath});
1677 void DrawTargetWebgl::PushClipRect(const Rect& aRect) {
1678 mClipChanged = true;
1679 mRefreshClipState = true;
1680 mSkia->PushClipRect(aRect);
1682 mClipStack.push_back({GetTransform(), aRect, nullptr});
1685 void DrawTargetWebgl::PushDeviceSpaceClipRects(const IntRect* aRects,
1686 uint32_t aCount) {
1687 mClipChanged = true;
1688 mRefreshClipState = true;
1689 mSkia->PushDeviceSpaceClipRects(aRects, aCount);
1691 for (uint32_t i = 0; i < aCount; i++) {
1692 mClipStack.push_back({Matrix(), Rect(aRects[i]), nullptr});
1696 void DrawTargetWebgl::PopClip() {
1697 mClipChanged = true;
1698 mRefreshClipState = true;
1699 mSkia->PopClip();
1701 mClipStack.pop_back();
1704 bool DrawTargetWebgl::RemoveAllClips() {
1705 if (mClipStack.empty()) {
1706 return true;
1708 if (!mSkia->RemoveAllClips()) {
1709 return false;
1711 mClipChanged = true;
1712 mRefreshClipState = true;
1713 mClipStack.clear();
1714 return true;
1717 void DrawTargetWebgl::CopyToFallback(DrawTarget* aDT) {
1718 if (RefPtr<SourceSurface> snapshot = Snapshot()) {
1719 aDT->CopySurface(snapshot, snapshot->GetRect(), gfx::IntPoint(0, 0));
1721 aDT->RemoveAllClips();
1722 for (auto& clipStack : mClipStack) {
1723 aDT->SetTransform(clipStack.mTransform);
1724 if (clipStack.mPath) {
1725 aDT->PushClip(clipStack.mPath);
1726 } else {
1727 aDT->PushClipRect(clipStack.mRect);
1730 aDT->SetTransform(GetTransform());
1733 // Whether a given composition operator can be mapped to a WebGL blend mode.
1734 static inline bool SupportsDrawOptions(const DrawOptions& aOptions) {
1735 switch (aOptions.mCompositionOp) {
1736 case CompositionOp::OP_OVER:
1737 case CompositionOp::OP_ADD:
1738 case CompositionOp::OP_ATOP:
1739 case CompositionOp::OP_SOURCE:
1740 case CompositionOp::OP_CLEAR:
1741 return true;
1742 default:
1743 return false;
1747 // Whether a pattern can be mapped to an available WebGL shader.
1748 bool SharedContextWebgl::SupportsPattern(const Pattern& aPattern) {
1749 switch (aPattern.GetType()) {
1750 case PatternType::COLOR:
1751 return true;
1752 case PatternType::SURFACE: {
1753 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1754 if (surfacePattern.mExtendMode != ExtendMode::CLAMP) {
1755 return false;
1757 if (surfacePattern.mSurface) {
1758 // If the surface is already uploaded to a texture, then just use it.
1759 if (IsCompatibleSurface(surfacePattern.mSurface)) {
1760 return true;
1763 IntSize size = surfacePattern.mSurface->GetSize();
1764 // The maximum size a surface can be before triggering a fallback to
1765 // software. Bound the maximum surface size by the actual texture size
1766 // limit.
1767 int32_t maxSize = int32_t(
1768 std::min(StaticPrefs::gfx_canvas_accelerated_max_surface_size(),
1769 mMaxTextureSize));
1770 // Check if either of the surface dimensions or the sampling rect,
1771 // if supplied, exceed the maximum.
1772 if (std::max(size.width, size.height) > maxSize &&
1773 (surfacePattern.mSamplingRect.IsEmpty() ||
1774 std::max(surfacePattern.mSamplingRect.width,
1775 surfacePattern.mSamplingRect.height) > maxSize)) {
1776 return false;
1779 return true;
1781 default:
1782 // Patterns other than colors and surfaces are currently not accelerated.
1783 return false;
1787 bool DrawTargetWebgl::DrawRect(const Rect& aRect, const Pattern& aPattern,
1788 const DrawOptions& aOptions,
1789 Maybe<DeviceColor> aMaskColor,
1790 RefPtr<TextureHandle>* aHandle,
1791 bool aTransformed, bool aClipped,
1792 bool aAccelOnly, bool aForceUpdate,
1793 const StrokeOptions* aStrokeOptions) {
1794 // If there is nothing to draw, then don't draw...
1795 if (aRect.IsEmpty()) {
1796 return true;
1799 // If we're already drawing directly to the WebGL context, then we want to
1800 // continue to do so. However, if we're drawing into a Skia layer over the
1801 // WebGL context, then we need to be careful to avoid repeatedly clearing
1802 // and flushing the layer if we hit a drawing request that can be accelerated
1803 // in between layered drawing requests, as clearing and flushing the layer
1804 // can be significantly expensive when repeated. So when a Skia layer is
1805 // active, if it is possible to continue drawing into the layer, then don't
1806 // accelerate the drawing request.
1807 if (mWebglValid || (mSkiaLayer && !mLayerDepth &&
1808 (aAccelOnly || !SupportsLayering(aOptions)))) {
1809 // If we get here, either the WebGL context is being directly drawn to
1810 // or we are going to flush the Skia layer to it before doing so. The shared
1811 // context still needs to be claimed and prepared for drawing. If this
1812 // fails, we just fall back to drawing with Skia below.
1813 if (PrepareContext(aClipped)) {
1814 // The shared context is claimed and the framebuffer is now valid, so try
1815 // accelerated drawing.
1816 return mSharedContext->DrawRectAccel(
1817 aRect, aPattern, aOptions, aMaskColor, aHandle, aTransformed,
1818 aClipped, aAccelOnly, aForceUpdate, aStrokeOptions);
1822 // Either there is no valid WebGL target to draw into, or we failed to prepare
1823 // it for drawing. The only thing we can do at this point is fall back to
1824 // drawing with Skia. If the request explicitly requires accelerated drawing,
1825 // then draw nothing before returning failure.
1826 if (!aAccelOnly) {
1827 DrawRectFallback(aRect, aPattern, aOptions, aMaskColor, aTransformed,
1828 aClipped, aStrokeOptions);
1830 return false;
1833 void DrawTargetWebgl::DrawRectFallback(const Rect& aRect,
1834 const Pattern& aPattern,
1835 const DrawOptions& aOptions,
1836 Maybe<DeviceColor> aMaskColor,
1837 bool aTransformed, bool aClipped,
1838 const StrokeOptions* aStrokeOptions) {
1839 // Invalidate the WebGL target and prepare the Skia target for drawing.
1840 MarkSkiaChanged(aOptions);
1842 if (aTransformed) {
1843 // If transforms are requested, then just translate back to FillRect.
1844 if (aMaskColor) {
1845 mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions);
1846 } else if (aStrokeOptions) {
1847 mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions);
1848 } else {
1849 mSkia->FillRect(aRect, aPattern, aOptions);
1851 } else if (aClipped) {
1852 // If no transform was requested but clipping is still required, then
1853 // temporarily reset the transform before translating to FillRect.
1854 mSkia->SetTransform(Matrix());
1855 if (aMaskColor) {
1856 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1857 if (surfacePattern.mSamplingRect.IsEmpty()) {
1858 mSkia->MaskSurface(ColorPattern(*aMaskColor), surfacePattern.mSurface,
1859 aRect.TopLeft(), aOptions);
1860 } else {
1861 mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions);
1863 } else if (aStrokeOptions) {
1864 mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions);
1865 } else {
1866 mSkia->FillRect(aRect, aPattern, aOptions);
1868 mSkia->SetTransform(mTransform);
1869 } else if (aPattern.GetType() == PatternType::SURFACE) {
1870 // No transform nor clipping was requested, so it is essentially just a
1871 // copy.
1872 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1873 mSkia->CopySurface(surfacePattern.mSurface,
1874 surfacePattern.mSurface->GetRect(),
1875 IntPoint::Round(aRect.TopLeft()));
1876 } else {
1877 MOZ_ASSERT(false);
1881 inline already_AddRefed<WebGLTexture> SharedContextWebgl::GetCompatibleSnapshot(
1882 SourceSurface* aSurface) const {
1883 if (aSurface->GetType() == SurfaceType::WEBGL) {
1884 RefPtr<SourceSurfaceWebgl> webglSurf =
1885 static_cast<SourceSurfaceWebgl*>(aSurface);
1886 if (this == webglSurf->mSharedContext) {
1887 // If there is a snapshot copy in a texture handle, use that.
1888 if (webglSurf->mHandle) {
1889 return do_AddRef(
1890 webglSurf->mHandle->GetBackingTexture()->GetWebGLTexture());
1892 if (RefPtr<DrawTargetWebgl> webglDT = webglSurf->GetTarget()) {
1893 // If there is a copy-on-write reference to a target, use its backing
1894 // texture directly. This is only safe if the targets don't match, but
1895 // MarkChanged should ensure that any snapshots were copied into a
1896 // texture handle before we ever get here.
1897 if (!IsCurrentTarget(webglDT)) {
1898 return do_AddRef(webglDT->mTex);
1903 return nullptr;
1906 inline bool SharedContextWebgl::IsCompatibleSurface(
1907 SourceSurface* aSurface) const {
1908 return bool(RefPtr<WebGLTexture>(GetCompatibleSnapshot(aSurface)));
1911 bool SharedContextWebgl::UploadSurface(DataSourceSurface* aData,
1912 SurfaceFormat aFormat,
1913 const IntRect& aSrcRect,
1914 const IntPoint& aDstOffset, bool aInit,
1915 bool aZero,
1916 const RefPtr<WebGLTexture>& aTex) {
1917 webgl::TexUnpackBlobDesc texDesc = {
1918 LOCAL_GL_TEXTURE_2D,
1919 {uint32_t(aSrcRect.width), uint32_t(aSrcRect.height), 1}};
1920 if (aData) {
1921 // The surface needs to be uploaded to its backing texture either to
1922 // initialize or update the texture handle contents. Map the data
1923 // contents of the surface so it can be read.
1924 DataSourceSurface::ScopedMap map(aData, DataSourceSurface::READ);
1925 if (!map.IsMapped()) {
1926 return false;
1928 int32_t stride = map.GetStride();
1929 int32_t bpp = BytesPerPixel(aFormat);
1930 // Get the data pointer range considering the sampling rect offset and
1931 // size.
1932 Span<const uint8_t> range(
1933 map.GetData() + aSrcRect.y * size_t(stride) + aSrcRect.x * bpp,
1934 std::max(aSrcRect.height - 1, 0) * size_t(stride) +
1935 aSrcRect.width * bpp);
1936 texDesc.cpuData = Some(range);
1937 // If the stride happens to be 4 byte aligned, assume that is the
1938 // desired alignment regardless of format (even A8). Otherwise, we
1939 // default to byte alignment.
1940 texDesc.unpacking.alignmentInTypeElems = stride % 4 ? 1 : 4;
1941 texDesc.unpacking.rowLength = stride / bpp;
1942 } else if (aZero) {
1943 // Create a PBO filled with zero data to initialize the texture data and
1944 // avoid slow initialization inside WebGL.
1945 MOZ_ASSERT(aSrcRect.TopLeft() == IntPoint(0, 0));
1946 size_t size =
1947 size_t(GetAlignedStride<4>(aSrcRect.width, BytesPerPixel(aFormat))) *
1948 aSrcRect.height;
1949 if (!mZeroBuffer || size > mZeroSize) {
1950 mZeroBuffer = mWebgl->CreateBuffer();
1951 mZeroSize = size;
1952 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mZeroBuffer);
1953 // WebGL will zero initialize the empty buffer, so we don't send zero data
1954 // explicitly.
1955 mWebgl->BufferData(LOCAL_GL_PIXEL_UNPACK_BUFFER, size, nullptr,
1956 LOCAL_GL_STATIC_DRAW);
1957 } else {
1958 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mZeroBuffer);
1960 texDesc.pboOffset = Some(0);
1962 // Upload as RGBA8 to avoid swizzling during upload. Surfaces provide
1963 // data as BGRA, but we manually swizzle that in the shader. An A8
1964 // surface will be stored as an R8 texture that will also be swizzled
1965 // in the shader.
1966 GLenum intFormat =
1967 aFormat == SurfaceFormat::A8 ? LOCAL_GL_R8 : LOCAL_GL_RGBA8;
1968 GLenum extFormat =
1969 aFormat == SurfaceFormat::A8 ? LOCAL_GL_RED : LOCAL_GL_RGBA;
1970 webgl::PackingInfo texPI = {extFormat, LOCAL_GL_UNSIGNED_BYTE};
1971 // Do the (partial) upload for the shared or standalone texture.
1972 if (aTex) {
1973 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, aTex);
1975 mWebgl->TexImage(0, aInit ? intFormat : 0,
1976 {uint32_t(aDstOffset.x), uint32_t(aDstOffset.y), 0}, texPI,
1977 texDesc);
1978 if (aTex) {
1979 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mLastTexture);
1981 if (!aData && aZero) {
1982 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
1984 return true;
1987 // Allocate a new texture handle backed by either a standalone texture or as a
1988 // sub-texture of a larger shared texture.
1989 already_AddRefed<TextureHandle> SharedContextWebgl::AllocateTextureHandle(
1990 SurfaceFormat aFormat, const IntSize& aSize, bool aAllowShared,
1991 bool aRenderable) {
1992 RefPtr<TextureHandle> handle;
1993 // Calculate the bytes that would be used by this texture handle, and prune
1994 // enough other textures to ensure we have that much usable texture space
1995 // available to allocate.
1996 size_t usedBytes = BackingTexture::UsedBytes(aFormat, aSize);
1997 PruneTextureMemory(usedBytes, false);
1998 // The requested page size for shared textures.
1999 int32_t pageSize = int32_t(std::min(
2000 StaticPrefs::gfx_canvas_accelerated_shared_page_size(), mMaxTextureSize));
2001 if (aAllowShared && std::max(aSize.width, aSize.height) <= pageSize / 2) {
2002 // Ensure that the surface is no bigger than a quadrant of a shared texture
2003 // page. If so, try to allocate it to a shared texture. Look for any
2004 // existing shared texture page with a matching format and allocate
2005 // from that if possible.
2006 for (auto& shared : mSharedTextures) {
2007 if (shared->GetFormat() == aFormat &&
2008 shared->IsRenderable() == aRenderable) {
2009 bool wasEmpty = !shared->HasAllocatedHandles();
2010 handle = shared->Allocate(aSize);
2011 if (handle) {
2012 if (wasEmpty) {
2013 // If the page was previously empty, then deduct it from the
2014 // empty memory reserves.
2015 mEmptyTextureMemory -= shared->UsedBytes();
2017 break;
2021 // If we couldn't find an existing shared texture page with matching
2022 // format, then allocate a new page to put the request in.
2023 if (!handle) {
2024 if (RefPtr<WebGLTexture> tex = mWebgl->CreateTexture()) {
2025 RefPtr<SharedTexture> shared =
2026 new SharedTexture(IntSize(pageSize, pageSize), aFormat, tex);
2027 if (aRenderable) {
2028 shared->MarkRenderable();
2030 mSharedTextures.push_back(shared);
2031 mTotalTextureMemory += shared->UsedBytes();
2032 handle = shared->Allocate(aSize);
2035 } else {
2036 // The surface wouldn't fit in a shared texture page, so we need to
2037 // allocate a standalone texture for it instead.
2038 if (RefPtr<WebGLTexture> tex = mWebgl->CreateTexture()) {
2039 RefPtr<StandaloneTexture> standalone =
2040 new StandaloneTexture(aSize, aFormat, tex);
2041 if (aRenderable) {
2042 standalone->MarkRenderable();
2044 mStandaloneTextures.push_back(standalone);
2045 mTotalTextureMemory += standalone->UsedBytes();
2046 handle = standalone;
2050 if (!handle) {
2051 return nullptr;
2054 // Insert the new texture handle into the front of the MRU list and
2055 // update used space for it.
2056 mTextureHandles.insertFront(handle);
2057 ++mNumTextureHandles;
2058 mUsedTextureMemory += handle->UsedBytes();
2060 return handle.forget();
2063 static inline SamplingFilter GetSamplingFilter(const Pattern& aPattern) {
2064 return aPattern.GetType() == PatternType::SURFACE
2065 ? static_cast<const SurfacePattern&>(aPattern).mSamplingFilter
2066 : SamplingFilter::GOOD;
2069 static inline bool UseNearestFilter(const Pattern& aPattern) {
2070 return GetSamplingFilter(aPattern) == SamplingFilter::POINT;
2073 // Determine if the rectangle is still axis-aligned and pixel-aligned.
2074 static inline Maybe<IntRect> IsAlignedRect(bool aTransformed,
2075 const Matrix& aCurrentTransform,
2076 const Rect& aRect) {
2077 if (!aTransformed || aCurrentTransform.HasOnlyIntegerTranslation()) {
2078 auto intRect = RoundedToInt(aRect);
2079 if (aRect.WithinEpsilonOf(Rect(intRect), 1.0e-3f)) {
2080 if (aTransformed) {
2081 intRect += RoundedToInt(aCurrentTransform.GetTranslation());
2083 return Some(intRect);
2086 return Nothing();
2089 Maybe<uint32_t> SharedContextWebgl::GetUniformLocation(
2090 const RefPtr<WebGLProgram>& aProg, const std::string& aName) const {
2091 if (!aProg || !aProg->LinkInfo()) {
2092 return Nothing();
2095 for (const auto& activeUniform : aProg->LinkInfo()->active.activeUniforms) {
2096 if (activeUniform.block_index != -1) continue;
2098 auto locName = activeUniform.name;
2099 const auto indexed = webgl::ParseIndexed(locName);
2100 if (indexed) {
2101 locName = indexed->name;
2104 const auto baseLength = locName.size();
2105 for (const auto& pair : activeUniform.locByIndex) {
2106 if (indexed) {
2107 locName.erase(baseLength); // Erase previous "[N]".
2108 locName += '[';
2109 locName += std::to_string(pair.first);
2110 locName += ']';
2112 if (locName == aName || locName == aName + "[0]") {
2113 return Some(pair.second);
2118 return Nothing();
2121 template <class T>
2122 struct IsUniformDataValT : std::false_type {};
2123 template <>
2124 struct IsUniformDataValT<webgl::UniformDataVal> : std::true_type {};
2125 template <>
2126 struct IsUniformDataValT<float> : std::true_type {};
2127 template <>
2128 struct IsUniformDataValT<int32_t> : std::true_type {};
2129 template <>
2130 struct IsUniformDataValT<uint32_t> : std::true_type {};
2132 template <typename T, typename = std::enable_if_t<IsUniformDataValT<T>::value>>
2133 inline Range<const webgl::UniformDataVal> AsUniformDataVal(
2134 const Range<const T>& data) {
2135 return {data.begin().template ReinterpretCast<const webgl::UniformDataVal>(),
2136 data.end().template ReinterpretCast<const webgl::UniformDataVal>()};
2139 template <class T, size_t N>
2140 inline void SharedContextWebgl::UniformData(GLenum aFuncElemType,
2141 const Maybe<uint32_t>& aLoc,
2142 const Array<T, N>& aData) {
2143 // We currently always pass false for transpose. If in the future we need
2144 // support for transpose then caching needs to take that in to account.
2145 mWebgl->UniformData(*aLoc, false,
2146 AsUniformDataVal(Range<const T>(Span<const T>(aData))));
2149 template <class T, size_t N>
2150 void SharedContextWebgl::MaybeUniformData(GLenum aFuncElemType,
2151 const Maybe<uint32_t>& aLoc,
2152 const Array<T, N>& aData,
2153 Maybe<Array<T, N>>& aCached) {
2154 if (aCached.isNothing() || !(*aCached == aData)) {
2155 aCached = Some(aData);
2156 UniformData(aFuncElemType, aLoc, aData);
2160 inline void SharedContextWebgl::DrawQuad() {
2161 mWebgl->DrawArraysInstanced(LOCAL_GL_TRIANGLE_FAN, 0, 4, 1);
2164 void SharedContextWebgl::DrawTriangles(const PathVertexRange& aRange) {
2165 mWebgl->DrawArraysInstanced(LOCAL_GL_TRIANGLES, GLint(aRange.mOffset),
2166 GLsizei(aRange.mLength), 1);
2169 // Common rectangle and pattern drawing function shared by many DrawTarget
2170 // commands. If aMaskColor is specified, the provided surface pattern will be
2171 // treated as a mask. If aHandle is specified, then the surface pattern's
2172 // texture will be cached in the supplied handle, as opposed to using the
2173 // surface's user data. If aTransformed or aClipped are false, then transforms
2174 // and/or clipping will be disabled. If aAccelOnly is specified, then this
2175 // function will return before it would have otherwise drawn without
2176 // acceleration. If aForceUpdate is specified, then the provided texture handle
2177 // will be respecified with the provided surface.
2178 bool SharedContextWebgl::DrawRectAccel(
2179 const Rect& aRect, const Pattern& aPattern, const DrawOptions& aOptions,
2180 Maybe<DeviceColor> aMaskColor, RefPtr<TextureHandle>* aHandle,
2181 bool aTransformed, bool aClipped, bool aAccelOnly, bool aForceUpdate,
2182 const StrokeOptions* aStrokeOptions, const PathVertexRange* aVertexRange,
2183 const Matrix* aRectXform) {
2184 // If the rect or clip rect is empty, then there is nothing to draw.
2185 if (aRect.IsEmpty() || mClipRect.IsEmpty()) {
2186 return true;
2189 // Check if the drawing options and the pattern support acceleration. Also
2190 // ensure the framebuffer is prepared for drawing. If not, fall back to using
2191 // the Skia target. When we need to forcefully update a texture, we must be
2192 // careful to override any pattern limits, as the caller ensures the pattern
2193 // is otherwise a supported type.
2194 if (!SupportsDrawOptions(aOptions) ||
2195 (!aForceUpdate && !SupportsPattern(aPattern)) || aStrokeOptions ||
2196 !mCurrentTarget->MarkChanged()) {
2197 // If only accelerated drawing was requested, bail out without software
2198 // drawing fallback.
2199 if (!aAccelOnly) {
2200 MOZ_ASSERT(!aVertexRange);
2201 mCurrentTarget->DrawRectFallback(aRect, aPattern, aOptions, aMaskColor,
2202 aTransformed, aClipped, aStrokeOptions);
2204 return false;
2207 const Matrix& currentTransform = mCurrentTarget->GetTransform();
2208 // rectXform only applies to the rect, but should not apply to the pattern,
2209 // as it might inadvertently alter the pattern.
2210 Matrix rectXform = currentTransform;
2211 if (aRectXform) {
2212 rectXform.PreMultiply(*aRectXform);
2215 if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE && aTransformed &&
2216 aClipped &&
2217 (HasClipMask() || !rectXform.PreservesAxisAlignedRectangles() ||
2218 !rectXform.TransformBounds(aRect).Contains(Rect(mClipAARect)) ||
2219 (aPattern.GetType() == PatternType::SURFACE &&
2220 !IsAlignedRect(aTransformed, rectXform, aRect)))) {
2221 // Clear outside the mask region for masks that are not bounded by clip.
2222 return DrawRectAccel(Rect(mClipRect), ColorPattern(DeviceColor(0, 0, 0, 0)),
2223 DrawOptions(1.0f, CompositionOp::OP_SOURCE,
2224 aOptions.mAntialiasMode),
2225 Nothing(), nullptr, false, aClipped, aAccelOnly) &&
2226 DrawRectAccel(aRect, aPattern,
2227 DrawOptions(aOptions.mAlpha, CompositionOp::OP_ADD,
2228 aOptions.mAntialiasMode),
2229 aMaskColor, aHandle, aTransformed, aClipped,
2230 aAccelOnly, aForceUpdate, aStrokeOptions, aVertexRange,
2231 aRectXform);
2233 if (aOptions.mCompositionOp == CompositionOp::OP_CLEAR &&
2234 aPattern.GetType() == PatternType::SURFACE && !aMaskColor) {
2235 // If the surface being drawn with clear is not a mask, then its contents
2236 // needs to be ignored. Just use a color pattern instead.
2237 return DrawRectAccel(aRect, ColorPattern(DeviceColor(1, 1, 1, 1)), aOptions,
2238 Nothing(), aHandle, aTransformed, aClipped, aAccelOnly,
2239 aForceUpdate, aStrokeOptions, aVertexRange,
2240 aRectXform);
2243 // Set up the scissor test to reflect the clipping rectangle, if supplied.
2244 if (!mClipRect.Contains(IntRect(IntPoint(), mViewportSize))) {
2245 EnableScissor(mClipRect);
2246 } else {
2247 DisableScissor();
2250 bool success = false;
2252 // Now try to actually draw the pattern...
2253 switch (aPattern.GetType()) {
2254 case PatternType::COLOR: {
2255 if (!aVertexRange) {
2256 // Only an uncached draw if not using the vertex cache.
2257 mCurrentTarget->mProfile.OnUncachedDraw();
2259 DeviceColor color = PremultiplyColor(
2260 static_cast<const ColorPattern&>(aPattern).mColor, aOptions.mAlpha);
2261 if (((color.a == 1.0f &&
2262 aOptions.mCompositionOp == CompositionOp::OP_OVER) ||
2263 aOptions.mCompositionOp == CompositionOp::OP_SOURCE ||
2264 aOptions.mCompositionOp == CompositionOp::OP_CLEAR) &&
2265 !aStrokeOptions && !aVertexRange && !HasClipMask() &&
2266 mClipAARect.IsEqualEdges(Rect(mClipRect))) {
2267 // Certain color patterns can be mapped to scissored clears. The
2268 // composition op must effectively overwrite the destination, and the
2269 // transform must map to an axis-aligned integer rectangle.
2270 if (Maybe<IntRect> intRect =
2271 IsAlignedRect(aTransformed, rectXform, aRect)) {
2272 // Only use a clear if the area is larger than a quarter or the
2273 // viewport.
2274 if (intRect->Area() >=
2275 (mViewportSize.width / 2) * (mViewportSize.height / 2)) {
2276 if (!intRect->Contains(mClipRect)) {
2277 EnableScissor(intRect->Intersect(mClipRect));
2279 if (aOptions.mCompositionOp == CompositionOp::OP_CLEAR) {
2280 color =
2281 PremultiplyColor(mCurrentTarget->GetClearPattern().mColor);
2283 mWebgl->ClearColor(color.b, color.g, color.r, color.a);
2284 mWebgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT);
2285 success = true;
2286 break;
2290 // Map the composition op to a WebGL blend mode, if possible.
2291 Maybe<DeviceColor> blendColor;
2292 if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE ||
2293 aOptions.mCompositionOp == CompositionOp::OP_CLEAR) {
2294 // The source operator can support clipping and AA by emulating it with
2295 // the over op. Supply the color with blend state, and set the shader
2296 // color to white, to avoid needing dual-source blending.
2297 blendColor = Some(color);
2298 // Both source and clear operators should output a mask from the shader.
2299 color = DeviceColor(1, 1, 1, 1);
2301 SetBlendState(aOptions.mCompositionOp, blendColor);
2302 // Since it couldn't be mapped to a scissored clear, we need to use the
2303 // solid color shader with supplied transform.
2304 if (mLastProgram != mSolidProgram) {
2305 mWebgl->UseProgram(mSolidProgram);
2306 mLastProgram = mSolidProgram;
2308 Array<float, 2> viewportData = {float(mViewportSize.width),
2309 float(mViewportSize.height)};
2310 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramViewport, viewportData,
2311 mSolidProgramUniformState.mViewport);
2313 // Generated paths provide their own AA as vertex alpha.
2314 Array<float, 1> aaData = {aVertexRange ? 0.0f : 1.0f};
2315 MaybeUniformData(LOCAL_GL_FLOAT, mSolidProgramAA, aaData,
2316 mSolidProgramUniformState.mAA);
2318 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel
2319 // boundary.
2320 Array<float, 4> clipData = {mClipAARect.x - 0.5f, mClipAARect.y - 0.5f,
2321 mClipAARect.XMost() + 0.5f,
2322 mClipAARect.YMost() + 0.5f};
2323 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramClipBounds, clipData,
2324 mSolidProgramUniformState.mClipBounds);
2326 Array<float, 4> colorData = {color.b, color.g, color.r, color.a};
2327 Matrix xform(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y);
2328 if (aTransformed) {
2329 xform *= rectXform;
2331 Array<float, 6> xformData = {xform._11, xform._12, xform._21,
2332 xform._22, xform._31, xform._32};
2333 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramTransform, xformData,
2334 mSolidProgramUniformState.mTransform);
2336 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramColor, colorData,
2337 mSolidProgramUniformState.mColor);
2339 // Finally draw the colored rectangle.
2340 if (aVertexRange) {
2341 // If there's a vertex range, then we need to draw triangles within from
2342 // generated from a path stored in the path vertex buffer.
2343 DrawTriangles(*aVertexRange);
2344 } else {
2345 // Otherwise we're drawing a simple filled rectangle.
2346 DrawQuad();
2348 success = true;
2349 break;
2351 case PatternType::SURFACE: {
2352 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
2353 // If a texture handle was supplied, or if the surface already has an
2354 // assigned texture handle stashed in its used data, try to use it.
2355 RefPtr<TextureHandle> handle =
2356 aHandle ? aHandle->get()
2357 : (surfacePattern.mSurface
2358 ? static_cast<TextureHandle*>(
2359 surfacePattern.mSurface->GetUserData(
2360 &mTextureHandleKey))
2361 : nullptr);
2362 IntSize texSize;
2363 IntPoint offset;
2364 SurfaceFormat format;
2365 // Check if the found handle is still valid and if its sampling rect
2366 // matches the requested sampling rect.
2367 if (handle && handle->IsValid() &&
2368 (surfacePattern.mSamplingRect.IsEmpty() ||
2369 handle->GetSamplingRect().IsEqualEdges(
2370 surfacePattern.mSamplingRect))) {
2371 texSize = handle->GetSize();
2372 format = handle->GetFormat();
2373 offset = handle->GetSamplingOffset();
2374 } else {
2375 // Otherwise, there is no handle that can be used yet, so extract
2376 // information from the surface pattern.
2377 handle = nullptr;
2378 if (!surfacePattern.mSurface) {
2379 // If there was no actual surface supplied, then we tried to draw
2380 // using a texture handle, but the texture handle wasn't valid.
2381 break;
2383 texSize = surfacePattern.mSurface->GetSize();
2384 format = surfacePattern.mSurface->GetFormat();
2385 if (!surfacePattern.mSamplingRect.IsEmpty()) {
2386 texSize = surfacePattern.mSamplingRect.Size();
2387 offset = surfacePattern.mSamplingRect.TopLeft();
2391 // We need to be able to transform from local space into texture space.
2392 Matrix invMatrix = surfacePattern.mMatrix;
2393 // If drawing a pre-transformed vertex range, then we need to ensure the
2394 // user-space pattern is still transformed to screen-space.
2395 if (aVertexRange && !aTransformed) {
2396 invMatrix *= currentTransform;
2398 if (!invMatrix.Invert()) {
2399 break;
2401 if (aRectXform) {
2402 // If there is aRectXform, it must be applied to the source rectangle to
2403 // generate the proper input coordinates for the inverse pattern matrix.
2404 invMatrix.PreMultiply(*aRectXform);
2407 RefPtr<WebGLTexture> tex;
2408 IntRect bounds;
2409 IntSize backingSize;
2410 RefPtr<DataSourceSurface> data;
2411 if (handle) {
2412 if (aForceUpdate) {
2413 data = surfacePattern.mSurface->GetDataSurface();
2414 if (!data) {
2415 break;
2417 // The size of the texture may change if we update contents.
2418 mUsedTextureMemory -= handle->UsedBytes();
2419 handle->UpdateSize(texSize);
2420 mUsedTextureMemory += handle->UsedBytes();
2421 handle->SetSamplingOffset(surfacePattern.mSamplingRect.TopLeft());
2423 // If using an existing handle, move it to the front of the MRU list.
2424 handle->remove();
2425 mTextureHandles.insertFront(handle);
2426 } else if ((tex = GetCompatibleSnapshot(surfacePattern.mSurface))) {
2427 backingSize = surfacePattern.mSurface->GetSize();
2428 bounds = IntRect(offset, texSize);
2429 // Count reusing a snapshot texture (no readback) as a cache hit.
2430 mCurrentTarget->mProfile.OnCacheHit();
2431 } else {
2432 // If we get here, we need a data surface for a texture upload.
2433 data = surfacePattern.mSurface->GetDataSurface();
2434 if (!data) {
2435 break;
2437 // There is no existing handle. Try to allocate a new one. If the
2438 // surface size may change via a forced update, then don't allocate
2439 // from a shared texture page.
2440 handle = AllocateTextureHandle(format, texSize, !aForceUpdate);
2441 if (!handle) {
2442 MOZ_ASSERT(false);
2443 break;
2445 // Link the handle to the surface's user data.
2446 handle->SetSamplingOffset(surfacePattern.mSamplingRect.TopLeft());
2447 if (aHandle) {
2448 *aHandle = handle;
2449 } else {
2450 handle->SetSurface(surfacePattern.mSurface);
2451 surfacePattern.mSurface->AddUserData(&mTextureHandleKey, handle.get(),
2452 nullptr);
2456 // Map the composition op to a WebGL blend mode, if possible. If there is
2457 // a mask color and a texture with multiple channels, assume subpixel
2458 // blending. If we encounter the source op here, then assume the surface
2459 // is opaque (non-opaque is handled above) and emulate it with over.
2460 SetBlendState(aOptions.mCompositionOp,
2461 format != SurfaceFormat::A8 ? aMaskColor : Nothing());
2462 // Switch to the image shader and set up relevant transforms.
2463 if (mLastProgram != mImageProgram) {
2464 mWebgl->UseProgram(mImageProgram);
2465 mLastProgram = mImageProgram;
2468 Array<float, 2> viewportData = {float(mViewportSize.width),
2469 float(mViewportSize.height)};
2470 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mImageProgramViewport, viewportData,
2471 mImageProgramUniformState.mViewport);
2473 // AA is not supported for OP_SOURCE. Generated paths provide their own
2474 // AA as vertex alpha.
2475 Array<float, 1> aaData = {
2476 mLastCompositionOp == CompositionOp::OP_SOURCE || aVertexRange
2477 ? 0.0f
2478 : 1.0f};
2479 MaybeUniformData(LOCAL_GL_FLOAT, mImageProgramAA, aaData,
2480 mImageProgramUniformState.mAA);
2482 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel
2483 // boundary.
2484 Array<float, 4> clipData = {mClipAARect.x - 0.5f, mClipAARect.y - 0.5f,
2485 mClipAARect.XMost() + 0.5f,
2486 mClipAARect.YMost() + 0.5f};
2487 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mImageProgramClipBounds, clipData,
2488 mImageProgramUniformState.mClipBounds);
2490 DeviceColor color =
2491 mLastCompositionOp == CompositionOp::OP_CLEAR
2492 ? DeviceColor(1, 1, 1, 1)
2493 : PremultiplyColor(
2494 aMaskColor && format != SurfaceFormat::A8
2495 ? DeviceColor::Mask(1.0f, aMaskColor->a)
2496 : aMaskColor.valueOr(DeviceColor(1, 1, 1, 1)),
2497 aOptions.mAlpha);
2498 Array<float, 4> colorData = {color.b, color.g, color.r, color.a};
2499 Array<float, 1> swizzleData = {format == SurfaceFormat::A8 ? 1.0f : 0.0f};
2500 Matrix xform(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y);
2501 if (aTransformed) {
2502 xform *= rectXform;
2504 Array<float, 6> xformData = {xform._11, xform._12, xform._21,
2505 xform._22, xform._31, xform._32};
2506 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mImageProgramTransform, xformData,
2507 mImageProgramUniformState.mTransform);
2509 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mImageProgramColor, colorData,
2510 mImageProgramUniformState.mColor);
2512 MaybeUniformData(LOCAL_GL_FLOAT, mImageProgramSwizzle, swizzleData,
2513 mImageProgramUniformState.mSwizzle);
2515 // Start binding the WebGL state for the texture.
2516 BackingTexture* backing = nullptr;
2517 if (handle) {
2518 backing = handle->GetBackingTexture();
2519 if (!tex) {
2520 tex = backing->GetWebGLTexture();
2522 bounds = handle->GetBounds();
2523 backingSize = backing->GetSize();
2525 if (mLastTexture != tex) {
2526 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex);
2527 mLastTexture = tex;
2530 if (backing && !backing->IsInitialized()) {
2531 // If this is the first time the texture is used, we need to initialize
2532 // the clamping and filtering state.
2533 backing->MarkInitialized();
2534 InitTexParameters(tex);
2535 if (texSize != backingSize) {
2536 // If this is a shared texture handle whose actual backing texture is
2537 // larger than it, then we need to allocate the texture page to the
2538 // full backing size before we can do a partial upload of the surface.
2539 UploadSurface(nullptr, format, IntRect(IntPoint(), backingSize),
2540 IntPoint(), true, true);
2544 if (data) {
2545 UploadSurface(data, format, IntRect(offset, texSize), bounds.TopLeft(),
2546 texSize == backingSize);
2547 // Signal that we had to upload new data to the texture cache.
2548 mCurrentTarget->mProfile.OnCacheMiss();
2549 } else {
2550 // Signal that we are reusing data from the texture cache.
2551 mCurrentTarget->mProfile.OnCacheHit();
2554 // Set up the texture coordinate matrix to map from the input rectangle to
2555 // the backing texture subrect.
2556 Size backingSizeF(backingSize);
2557 Matrix uvMatrix(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y);
2558 uvMatrix *= invMatrix;
2559 uvMatrix *= Matrix(1.0f / backingSizeF.width, 0.0f, 0.0f,
2560 1.0f / backingSizeF.height,
2561 float(bounds.x - offset.x) / backingSizeF.width,
2562 float(bounds.y - offset.y) / backingSizeF.height);
2563 Array<float, 6> uvData = {uvMatrix._11, uvMatrix._12, uvMatrix._21,
2564 uvMatrix._22, uvMatrix._31, uvMatrix._32};
2565 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mImageProgramTexMatrix, uvData,
2566 mImageProgramUniformState.mTexMatrix);
2568 // Clamp sampling to within the bounds of the backing texture subrect.
2569 Array<float, 4> texBounds = {
2570 (bounds.x + 0.5f) / backingSizeF.width,
2571 (bounds.y + 0.5f) / backingSizeF.height,
2572 (bounds.XMost() - 0.5f) / backingSizeF.width,
2573 (bounds.YMost() - 0.5f) / backingSizeF.height,
2575 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mImageProgramTexBounds, texBounds,
2576 mImageProgramUniformState.mTexBounds);
2578 // Ensure we use nearest filtering when no antialiasing is requested.
2579 if (UseNearestFilter(surfacePattern)) {
2580 SetTexFilter(tex, false);
2583 // Finally draw the image rectangle.
2584 if (aVertexRange) {
2585 // If there's a vertex range, then we need to draw triangles within from
2586 // generated from a path stored in the path vertex buffer.
2587 DrawTriangles(*aVertexRange);
2588 } else {
2589 // Otherwise we're drawing a simple filled rectangle.
2590 DrawQuad();
2593 // Restore the default linear filter if overridden.
2594 if (UseNearestFilter(surfacePattern)) {
2595 SetTexFilter(tex, true);
2598 success = true;
2599 break;
2601 default:
2602 gfxWarning() << "Unknown DrawTargetWebgl::DrawRect pattern type: "
2603 << (int)aPattern.GetType();
2604 break;
2607 return success;
2610 bool SharedContextWebgl::RemoveSharedTexture(
2611 const RefPtr<SharedTexture>& aTexture) {
2612 auto pos =
2613 std::find(mSharedTextures.begin(), mSharedTextures.end(), aTexture);
2614 if (pos == mSharedTextures.end()) {
2615 return false;
2617 // Keep around a reserve of empty pages to avoid initialization costs from
2618 // allocating shared pages. If still below the limit of reserved pages, then
2619 // just add it to the reserve. Otherwise, erase the empty texture page.
2620 size_t maxBytes = StaticPrefs::gfx_canvas_accelerated_reserve_empty_cache()
2621 << 20;
2622 size_t usedBytes = aTexture->UsedBytes();
2623 if (mEmptyTextureMemory + usedBytes <= maxBytes) {
2624 mEmptyTextureMemory += usedBytes;
2625 } else {
2626 mTotalTextureMemory -= usedBytes;
2627 mSharedTextures.erase(pos);
2628 ClearLastTexture();
2630 return true;
2633 void SharedTextureHandle::Cleanup(SharedContextWebgl& aContext) {
2634 mTexture->Free(*this);
2636 // Check if the shared handle's owning page has no more allocated handles
2637 // after we freed it. If so, remove the empty shared texture page also.
2638 if (!mTexture->HasAllocatedHandles()) {
2639 aContext.RemoveSharedTexture(mTexture);
2643 bool SharedContextWebgl::RemoveStandaloneTexture(
2644 const RefPtr<StandaloneTexture>& aTexture) {
2645 auto pos = std::find(mStandaloneTextures.begin(), mStandaloneTextures.end(),
2646 aTexture);
2647 if (pos == mStandaloneTextures.end()) {
2648 return false;
2650 mTotalTextureMemory -= aTexture->UsedBytes();
2651 mStandaloneTextures.erase(pos);
2652 ClearLastTexture();
2653 return true;
2656 void StandaloneTexture::Cleanup(SharedContextWebgl& aContext) {
2657 aContext.RemoveStandaloneTexture(this);
2660 // Prune a given texture handle and release its associated resources.
2661 void SharedContextWebgl::PruneTextureHandle(
2662 const RefPtr<TextureHandle>& aHandle) {
2663 // Invalidate the handle so nothing will subsequently use its contents.
2664 aHandle->Invalidate();
2665 // If the handle has an associated SourceSurface, unlink it.
2666 UnlinkSurfaceTexture(aHandle);
2667 // If the handle has an associated CacheEntry, unlink it.
2668 if (RefPtr<CacheEntry> entry = aHandle->GetCacheEntry()) {
2669 entry->Unlink();
2671 // Deduct the used space from the total.
2672 mUsedTextureMemory -= aHandle->UsedBytes();
2673 // Ensure any allocated shared or standalone texture regions get freed.
2674 aHandle->Cleanup(*this);
2677 // Prune any texture memory above the limit (or margin below the limit) or any
2678 // least-recently-used handles that are no longer associated with any usable
2679 // surface.
2680 bool SharedContextWebgl::PruneTextureMemory(size_t aMargin, bool aPruneUnused) {
2681 // The maximum amount of texture memory that may be used by textures.
2682 size_t maxBytes = StaticPrefs::gfx_canvas_accelerated_cache_size() << 20;
2683 maxBytes -= std::min(maxBytes, aMargin);
2684 size_t maxItems = StaticPrefs::gfx_canvas_accelerated_cache_items();
2685 size_t oldItems = mNumTextureHandles;
2686 while (!mTextureHandles.isEmpty() &&
2687 (mUsedTextureMemory > maxBytes || mNumTextureHandles > maxItems ||
2688 (aPruneUnused && !mTextureHandles.getLast()->IsUsed()))) {
2689 PruneTextureHandle(mTextureHandles.popLast());
2690 --mNumTextureHandles;
2692 return mNumTextureHandles < oldItems;
2695 void DrawTargetWebgl::FillRect(const Rect& aRect, const Pattern& aPattern,
2696 const DrawOptions& aOptions) {
2697 if (SupportsPattern(aPattern)) {
2698 if (RectInsidePrecisionLimits(aRect, mTransform)) {
2699 DrawRect(aRect, aPattern, aOptions);
2700 return;
2702 if (aPattern.GetType() == PatternType::COLOR &&
2703 RectContainsViewport(aRect)) {
2704 // If the pattern is transform-invariant and the rect encompasses the
2705 // entire viewport, just clip drawing to the viewport to avoid transform
2706 // issues.
2707 DrawRect(Rect(GetRect()), aPattern, aOptions, Nothing(), nullptr, false);
2708 return;
2711 if (!mWebglValid) {
2712 MarkSkiaChanged(aOptions);
2713 mSkia->FillRect(aRect, aPattern, aOptions);
2714 } else {
2715 // If the pattern is unsupported, then transform the rect to a path so it
2716 // can be cached.
2717 SkPath skiaPath;
2718 skiaPath.addRect(RectToSkRect(aRect));
2719 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
2720 DrawPath(path, aPattern, aOptions);
2724 void CacheEntry::Link(const RefPtr<TextureHandle>& aHandle) {
2725 mHandle = aHandle;
2726 mHandle->SetCacheEntry(this);
2729 // When the CacheEntry becomes unused, it marks the corresponding
2730 // TextureHandle as unused and unlinks it from the CacheEntry. The
2731 // entry is removed from its containing Cache, if applicable.
2732 void CacheEntry::Unlink() {
2733 // The entry may not have a valid handle if rasterization failed.
2734 if (mHandle) {
2735 mHandle->SetCacheEntry(nullptr);
2736 mHandle = nullptr;
2739 RemoveFromList();
2742 // Hashes a path and pattern to a single hash value that can be used for quick
2743 // comparisons. This currently avoids to expensive hashing of internal path
2744 // and pattern data for speed, relying instead on later exact comparisons for
2745 // disambiguation.
2746 HashNumber PathCacheEntry::HashPath(const QuantizedPath& aPath,
2747 const Pattern* aPattern,
2748 const Matrix& aTransform,
2749 const IntRect& aBounds,
2750 const Point& aOrigin) {
2751 HashNumber hash = 0;
2752 hash = AddToHash(hash, aPath.mPath.num_types);
2753 hash = AddToHash(hash, aPath.mPath.num_points);
2754 if (aPath.mPath.num_points > 0) {
2755 hash = AddToHash(hash, aPath.mPath.points[0].x);
2756 hash = AddToHash(hash, aPath.mPath.points[0].y);
2757 if (aPath.mPath.num_points > 1) {
2758 hash = AddToHash(hash, aPath.mPath.points[1].x);
2759 hash = AddToHash(hash, aPath.mPath.points[1].y);
2762 // Quantize the relative offset of the path to its bounds.
2763 IntPoint offset = RoundedToInt((aOrigin - Point(aBounds.TopLeft())) * 16.0f);
2764 hash = AddToHash(hash, offset.x);
2765 hash = AddToHash(hash, offset.y);
2766 hash = AddToHash(hash, aBounds.width);
2767 hash = AddToHash(hash, aBounds.height);
2768 if (aPattern) {
2769 hash = AddToHash(hash, (int)aPattern->GetType());
2771 return hash;
2774 // When caching rendered geometry, we need to ensure the scale and orientation
2775 // is approximately the same. The offset will be considered separately.
2776 static inline bool HasMatchingScale(const Matrix& aTransform1,
2777 const Matrix& aTransform2) {
2778 return FuzzyEqual(aTransform1._11, aTransform2._11) &&
2779 FuzzyEqual(aTransform1._12, aTransform2._12) &&
2780 FuzzyEqual(aTransform1._21, aTransform2._21) &&
2781 FuzzyEqual(aTransform1._22, aTransform2._22);
2784 // Determines if an existing path cache entry matches an incoming path and
2785 // pattern.
2786 inline bool PathCacheEntry::MatchesPath(const QuantizedPath& aPath,
2787 const Pattern* aPattern,
2788 const StrokeOptions* aStrokeOptions,
2789 const Matrix& aTransform,
2790 const IntRect& aBounds,
2791 const Point& aOrigin, HashNumber aHash,
2792 float aSigma) {
2793 return aHash == mHash && HasMatchingScale(aTransform, mTransform) &&
2794 // Ensure the clipped relative bounds fit inside those of the entry
2795 aBounds.x - aOrigin.x >= mBounds.x - mOrigin.x &&
2796 (aBounds.x - aOrigin.x) + aBounds.width <=
2797 (mBounds.x - mOrigin.x) + mBounds.width &&
2798 aBounds.y - aOrigin.y >= mBounds.y - mOrigin.y &&
2799 (aBounds.y - aOrigin.y) + aBounds.height <=
2800 (mBounds.y - mOrigin.y) + mBounds.height &&
2801 aPath == mPath &&
2802 (!aPattern ? !mPattern : mPattern && *aPattern == *mPattern) &&
2803 (!aStrokeOptions
2804 ? !mStrokeOptions
2805 : mStrokeOptions && *aStrokeOptions == *mStrokeOptions) &&
2806 aSigma == mSigma;
2809 PathCacheEntry::PathCacheEntry(QuantizedPath&& aPath, Pattern* aPattern,
2810 StoredStrokeOptions* aStrokeOptions,
2811 const Matrix& aTransform, const IntRect& aBounds,
2812 const Point& aOrigin, HashNumber aHash,
2813 float aSigma)
2814 : CacheEntryImpl<PathCacheEntry>(aTransform, aBounds, aHash),
2815 mPath(std::move(aPath)),
2816 mOrigin(aOrigin),
2817 mPattern(aPattern),
2818 mStrokeOptions(aStrokeOptions),
2819 mSigma(aSigma) {}
2821 // Attempt to find a matching entry in the path cache. If one isn't found,
2822 // a new entry will be created. The caller should check whether the contained
2823 // texture handle is valid to determine if it will need to render the text run
2824 // or just reuse the cached texture.
2825 already_AddRefed<PathCacheEntry> PathCache::FindOrInsertEntry(
2826 QuantizedPath aPath, const Pattern* aPattern,
2827 const StrokeOptions* aStrokeOptions, const Matrix& aTransform,
2828 const IntRect& aBounds, const Point& aOrigin, float aSigma) {
2829 HashNumber hash =
2830 PathCacheEntry::HashPath(aPath, aPattern, aTransform, aBounds, aOrigin);
2831 for (const RefPtr<PathCacheEntry>& entry : GetChain(hash)) {
2832 if (entry->MatchesPath(aPath, aPattern, aStrokeOptions, aTransform, aBounds,
2833 aOrigin, hash, aSigma)) {
2834 return do_AddRef(entry);
2837 Pattern* pattern = nullptr;
2838 if (aPattern) {
2839 pattern = aPattern->CloneWeak();
2840 if (!pattern) {
2841 return nullptr;
2844 StoredStrokeOptions* strokeOptions = nullptr;
2845 if (aStrokeOptions) {
2846 strokeOptions = aStrokeOptions->Clone();
2847 if (!strokeOptions) {
2848 return nullptr;
2851 RefPtr<PathCacheEntry> entry =
2852 new PathCacheEntry(std::move(aPath), pattern, strokeOptions, aTransform,
2853 aBounds, aOrigin, hash, aSigma);
2854 Insert(entry);
2855 return entry.forget();
2858 void DrawTargetWebgl::Fill(const Path* aPath, const Pattern& aPattern,
2859 const DrawOptions& aOptions) {
2860 if (!aPath || aPath->GetBackendType() != BackendType::SKIA) {
2861 return;
2864 const SkPath& skiaPath = static_cast<const PathSkia*>(aPath)->GetPath();
2865 SkRect skiaRect = SkRect::MakeEmpty();
2866 // Draw the path as a simple rectangle with a supported pattern when possible.
2867 if (skiaPath.isRect(&skiaRect) && SupportsPattern(aPattern)) {
2868 Rect rect = SkRectToRect(skiaRect);
2869 if (RectInsidePrecisionLimits(rect, mTransform)) {
2870 DrawRect(rect, aPattern, aOptions);
2871 return;
2873 if (aPattern.GetType() == PatternType::COLOR &&
2874 RectContainsViewport(rect)) {
2875 // If the pattern is transform-invariant and the rect encompasses the
2876 // entire viewport, just clip drawing to the viewport to avoid transform
2877 // issues.
2878 DrawRect(Rect(GetRect()), aPattern, aOptions, Nothing(), nullptr, false);
2879 return;
2883 DrawPath(aPath, aPattern, aOptions);
2886 void DrawTargetWebgl::FillCircle(const Point& aOrigin, float aRadius,
2887 const Pattern& aPattern,
2888 const DrawOptions& aOptions) {
2889 DrawCircle(aOrigin, aRadius, aPattern, aOptions);
2892 QuantizedPath::QuantizedPath(const WGR::Path& aPath) : mPath(aPath) {}
2894 QuantizedPath::QuantizedPath(QuantizedPath&& aPath) noexcept
2895 : mPath(aPath.mPath) {
2896 aPath.mPath.points = nullptr;
2897 aPath.mPath.num_points = 0;
2898 aPath.mPath.types = nullptr;
2899 aPath.mPath.num_types = 0;
2902 QuantizedPath::~QuantizedPath() {
2903 if (mPath.points || mPath.types) {
2904 WGR::wgr_path_release(mPath);
2908 bool QuantizedPath::operator==(const QuantizedPath& aOther) const {
2909 return mPath.num_types == aOther.mPath.num_types &&
2910 mPath.num_points == aOther.mPath.num_points &&
2911 mPath.fill_mode == aOther.mPath.fill_mode &&
2912 !memcmp(mPath.types, aOther.mPath.types,
2913 mPath.num_types * sizeof(uint8_t)) &&
2914 !memcmp(mPath.points, aOther.mPath.points,
2915 mPath.num_points * sizeof(WGR::Point));
2918 // Generate a quantized path from the Skia path using WGR. The supplied
2919 // transform will be applied to the path. The path is stored relative to its
2920 // bounds origin to support translation later.
2921 static Maybe<QuantizedPath> GenerateQuantizedPath(
2922 WGR::PathBuilder* aPathBuilder, const SkPath& aPath, const Rect& aBounds,
2923 const Matrix& aTransform) {
2924 if (!aPathBuilder) {
2925 return Nothing();
2928 WGR::wgr_builder_reset(aPathBuilder);
2929 WGR::wgr_builder_set_fill_mode(aPathBuilder,
2930 aPath.getFillType() == SkPathFillType::kWinding
2931 ? WGR::FillMode::Winding
2932 : WGR::FillMode::EvenOdd);
2934 SkPath::RawIter iter(aPath);
2935 SkPoint params[4];
2936 SkPath::Verb currentVerb;
2938 // printf_stderr("bounds: (%d, %d) %d x %d\n", aBounds.x, aBounds.y,
2939 // aBounds.width, aBounds.height);
2940 Matrix transform = aTransform;
2941 transform.PostTranslate(-aBounds.TopLeft());
2942 while ((currentVerb = iter.next(params)) != SkPath::kDone_Verb) {
2943 switch (currentVerb) {
2944 case SkPath::kMove_Verb: {
2945 Point p0 = transform.TransformPoint(SkPointToPoint(params[0]));
2946 WGR::wgr_builder_move_to(aPathBuilder, p0.x, p0.y);
2947 break;
2949 case SkPath::kLine_Verb: {
2950 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2951 WGR::wgr_builder_line_to(aPathBuilder, p1.x, p1.y);
2952 break;
2954 case SkPath::kCubic_Verb: {
2955 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2956 Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
2957 Point p3 = transform.TransformPoint(SkPointToPoint(params[3]));
2958 // printf_stderr("cubic (%f, %f), (%f, %f), (%f, %f)\n", p1.x, p1.y,
2959 // p2.x, p2.y, p3.x, p3.y);
2960 WGR::wgr_builder_curve_to(aPathBuilder, p1.x, p1.y, p2.x, p2.y, p3.x,
2961 p3.y);
2962 break;
2964 case SkPath::kQuad_Verb: {
2965 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2966 Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
2967 // printf_stderr("quad (%f, %f), (%f, %f)\n", p1.x, p1.y, p2.x, p2.y);
2968 WGR::wgr_builder_quad_to(aPathBuilder, p1.x, p1.y, p2.x, p2.y);
2969 break;
2971 case SkPath::kConic_Verb: {
2972 Point p0 = transform.TransformPoint(SkPointToPoint(params[0]));
2973 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2974 Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
2975 float w = iter.conicWeight();
2976 std::vector<Point> quads;
2977 int numQuads = ConvertConicToQuads(p0, p1, p2, w, quads);
2978 for (int i = 0; i < numQuads; i++) {
2979 Point q1 = quads[2 * i + 1];
2980 Point q2 = quads[2 * i + 2];
2981 // printf_stderr("conic quad (%f, %f), (%f, %f)\n", q1.x, q1.y, q2.x,
2982 // q2.y);
2983 WGR::wgr_builder_quad_to(aPathBuilder, q1.x, q1.y, q2.x, q2.y);
2985 break;
2987 case SkPath::kClose_Verb:
2988 // printf_stderr("close\n");
2989 WGR::wgr_builder_close(aPathBuilder);
2990 break;
2991 default:
2992 MOZ_ASSERT(false);
2993 // Unexpected verb found in path!
2994 return Nothing();
2998 WGR::Path p = WGR::wgr_builder_get_path(aPathBuilder);
2999 if (!p.num_points || !p.num_types) {
3000 WGR::wgr_path_release(p);
3001 return Nothing();
3003 return Some(QuantizedPath(p));
3006 // Get the output vertex buffer using WGR from an input quantized path.
3007 static Maybe<WGR::VertexBuffer> GeneratePathVertexBuffer(
3008 const QuantizedPath& aPath, const IntRect& aClipRect,
3009 bool aRasterizationTruncates, WGR::OutputVertex* aBuffer,
3010 size_t aBufferCapacity) {
3011 WGR::VertexBuffer vb = WGR::wgr_path_rasterize_to_tri_list(
3012 &aPath.mPath, aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height,
3013 true, false, aRasterizationTruncates, aBuffer, aBufferCapacity);
3014 if (!vb.len || (aBuffer && vb.len > aBufferCapacity)) {
3015 WGR::wgr_vertex_buffer_release(vb);
3016 return Nothing();
3018 return Some(vb);
3021 static inline AAStroke::LineJoin ToAAStrokeLineJoin(JoinStyle aJoin) {
3022 switch (aJoin) {
3023 case JoinStyle::BEVEL:
3024 return AAStroke::LineJoin::Bevel;
3025 case JoinStyle::ROUND:
3026 return AAStroke::LineJoin::Round;
3027 case JoinStyle::MITER:
3028 case JoinStyle::MITER_OR_BEVEL:
3029 return AAStroke::LineJoin::Miter;
3031 return AAStroke::LineJoin::Miter;
3034 static inline AAStroke::LineCap ToAAStrokeLineCap(CapStyle aCap) {
3035 switch (aCap) {
3036 case CapStyle::BUTT:
3037 return AAStroke::LineCap::Butt;
3038 case CapStyle::ROUND:
3039 return AAStroke::LineCap::Round;
3040 case CapStyle::SQUARE:
3041 return AAStroke::LineCap::Square;
3043 return AAStroke::LineCap::Butt;
3046 static inline Point WGRPointToPoint(const WGR::Point& aPoint) {
3047 return Point(IntPoint(aPoint.x, aPoint.y)) * (1.0f / 16.0f);
3050 // Generates a vertex buffer for a stroked path using aa-stroke.
3051 static Maybe<AAStroke::VertexBuffer> GenerateStrokeVertexBuffer(
3052 const QuantizedPath& aPath, const StrokeOptions* aStrokeOptions,
3053 float aScale, WGR::OutputVertex* aBuffer, size_t aBufferCapacity) {
3054 AAStroke::StrokeStyle style = {aStrokeOptions->mLineWidth * aScale,
3055 ToAAStrokeLineCap(aStrokeOptions->mLineCap),
3056 ToAAStrokeLineJoin(aStrokeOptions->mLineJoin),
3057 aStrokeOptions->mMiterLimit};
3058 if (style.width <= 0.0f || !std::isfinite(style.width) ||
3059 style.miter_limit <= 0.0f || !std::isfinite(style.miter_limit)) {
3060 return Nothing();
3062 AAStroke::Stroker* s = AAStroke::aa_stroke_new(
3063 &style, (AAStroke::OutputVertex*)aBuffer, aBufferCapacity);
3064 bool valid = true;
3065 size_t curPoint = 0;
3066 for (size_t curType = 0; valid && curType < aPath.mPath.num_types;) {
3067 // Verify that we are at the start of a sub-path.
3068 if ((aPath.mPath.types[curType] & WGR::PathPointTypePathTypeMask) !=
3069 WGR::PathPointTypeStart) {
3070 valid = false;
3071 break;
3073 // Find where the next sub-path starts so we can locate the end.
3074 size_t endType = curType + 1;
3075 for (; endType < aPath.mPath.num_types; endType++) {
3076 if ((aPath.mPath.types[endType] & WGR::PathPointTypePathTypeMask) ==
3077 WGR::PathPointTypeStart) {
3078 break;
3081 // Check if the path is closed. This is a flag modifying the last type.
3082 bool closed =
3083 (aPath.mPath.types[endType - 1] & WGR::PathPointTypeCloseSubpath) != 0;
3084 for (; curType < endType; curType++) {
3085 // If this is the last type and the sub-path is not closed, determine if
3086 // this segment should be capped.
3087 bool end = curType + 1 == endType && !closed;
3088 switch (aPath.mPath.types[curType] & WGR::PathPointTypePathTypeMask) {
3089 case WGR::PathPointTypeStart: {
3090 if (curPoint + 1 > aPath.mPath.num_points) {
3091 valid = false;
3092 break;
3094 Point p1 = WGRPointToPoint(aPath.mPath.points[curPoint]);
3095 AAStroke::aa_stroke_move_to(s, p1.x, p1.y, closed);
3096 if (end) {
3097 AAStroke::aa_stroke_line_to(s, p1.x, p1.y, true);
3099 curPoint++;
3100 break;
3102 case WGR::PathPointTypeLine: {
3103 if (curPoint + 1 > aPath.mPath.num_points) {
3104 valid = false;
3105 break;
3107 Point p1 = WGRPointToPoint(aPath.mPath.points[curPoint]);
3108 AAStroke::aa_stroke_line_to(s, p1.x, p1.y, end);
3109 curPoint++;
3110 break;
3112 case WGR::PathPointTypeBezier: {
3113 if (curPoint + 3 > aPath.mPath.num_points) {
3114 valid = false;
3115 break;
3117 Point p1 = WGRPointToPoint(aPath.mPath.points[curPoint]);
3118 Point p2 = WGRPointToPoint(aPath.mPath.points[curPoint + 1]);
3119 Point p3 = WGRPointToPoint(aPath.mPath.points[curPoint + 2]);
3120 AAStroke::aa_stroke_curve_to(s, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y,
3121 end);
3122 curPoint += 3;
3123 break;
3125 default:
3126 MOZ_ASSERT(false, "Unknown WGR path point type");
3127 valid = false;
3128 break;
3131 // Close the sub-path if necessary.
3132 if (valid && closed) {
3133 AAStroke::aa_stroke_close(s);
3136 Maybe<AAStroke::VertexBuffer> result;
3137 if (valid) {
3138 AAStroke::VertexBuffer vb = AAStroke::aa_stroke_finish(s);
3139 if (!vb.len || (aBuffer && vb.len > aBufferCapacity)) {
3140 AAStroke::aa_stroke_vertex_buffer_release(vb);
3141 } else {
3142 result = Some(vb);
3145 AAStroke::aa_stroke_release(s);
3146 return result;
3149 // Search the path cache for any entries stored in the path vertex buffer and
3150 // remove them.
3151 void PathCache::ClearVertexRanges() {
3152 for (auto& chain : mChains) {
3153 PathCacheEntry* entry = chain.getFirst();
3154 while (entry) {
3155 PathCacheEntry* next = entry->getNext();
3156 if (entry->GetVertexRange().IsValid()) {
3157 entry->Unlink();
3159 entry = next;
3164 inline bool DrawTargetWebgl::ShouldAccelPath(
3165 const DrawOptions& aOptions, const StrokeOptions* aStrokeOptions) {
3166 return mWebglValid && SupportsDrawOptions(aOptions) && PrepareContext();
3169 enum class AAStrokeMode {
3170 Unsupported,
3171 Geometry,
3172 Mask,
3175 // For now, we only directly support stroking solid color patterns to limit
3176 // artifacts from blending of overlapping geometry generated by AAStroke. Other
3177 // types of patterns may be partially supported by rendering to a temporary
3178 // mask.
3179 static inline AAStrokeMode SupportsAAStroke(const Pattern& aPattern,
3180 const DrawOptions& aOptions,
3181 const StrokeOptions& aStrokeOptions,
3182 bool aAllowStrokeAlpha) {
3183 if (aStrokeOptions.mDashPattern) {
3184 return AAStrokeMode::Unsupported;
3186 switch (aOptions.mCompositionOp) {
3187 case CompositionOp::OP_SOURCE:
3188 return AAStrokeMode::Geometry;
3189 case CompositionOp::OP_OVER:
3190 if (aPattern.GetType() == PatternType::COLOR) {
3191 return static_cast<const ColorPattern&>(aPattern).mColor.a *
3192 aOptions.mAlpha <
3193 1.0f &&
3194 !aAllowStrokeAlpha
3195 ? AAStrokeMode::Mask
3196 : AAStrokeMode::Geometry;
3198 return AAStrokeMode::Unsupported;
3199 default:
3200 return AAStrokeMode::Unsupported;
3204 // Render an AA-Stroke'd vertex range into an R8 mask texture for subsequent
3205 // drawing.
3206 already_AddRefed<TextureHandle> SharedContextWebgl::DrawStrokeMask(
3207 const PathVertexRange& aVertexRange, const IntSize& aSize) {
3208 // Allocate a new texture handle to store the rendered mask.
3209 RefPtr<TextureHandle> handle =
3210 AllocateTextureHandle(SurfaceFormat::A8, aSize, true, true);
3211 if (!handle) {
3212 return nullptr;
3215 IntRect texBounds = handle->GetBounds();
3216 BackingTexture* backing = handle->GetBackingTexture();
3217 if (!backing->IsInitialized()) {
3218 // If the backing texture is uninitialized, it needs its sampling parameters
3219 // set for later use.
3220 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, backing->GetWebGLTexture());
3221 mWebgl->TexStorage(LOCAL_GL_TEXTURE_2D, 1, LOCAL_GL_R8,
3222 {uint32_t(backing->GetSize().width),
3223 uint32_t(backing->GetSize().height), 1});
3224 InitTexParameters(backing->GetWebGLTexture());
3225 ClearLastTexture();
3228 // Set up a scratch framebuffer to render to the appropriate sub-texture of
3229 // the backing texture.
3230 if (!mScratchFramebuffer) {
3231 mScratchFramebuffer = mWebgl->CreateFramebuffer();
3233 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mScratchFramebuffer);
3234 webgl::FbAttachInfo attachInfo;
3235 attachInfo.tex = backing->GetWebGLTexture();
3236 mWebgl->FramebufferAttach(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
3237 LOCAL_GL_TEXTURE_2D, attachInfo);
3238 mWebgl->Viewport(texBounds.x, texBounds.y, texBounds.width, texBounds.height);
3239 EnableScissor(texBounds);
3240 if (!backing->IsInitialized()) {
3241 backing->MarkInitialized();
3242 // WebGL implicitly clears the backing texture the first time it is used.
3243 } else {
3244 // Ensure the mask background is clear.
3245 mWebgl->ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
3246 mWebgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT);
3249 // Reset any blending when drawing the mask.
3250 SetBlendState(CompositionOp::OP_OVER);
3252 // Set up the solid color shader to draw a simple opaque mask.
3253 if (mLastProgram != mSolidProgram) {
3254 mWebgl->UseProgram(mSolidProgram);
3255 mLastProgram = mSolidProgram;
3257 Array<float, 2> viewportData = {float(texBounds.width),
3258 float(texBounds.height)};
3259 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramViewport, viewportData,
3260 mSolidProgramUniformState.mViewport);
3261 Array<float, 1> aaData = {0.0f};
3262 MaybeUniformData(LOCAL_GL_FLOAT, mSolidProgramAA, aaData,
3263 mSolidProgramUniformState.mAA);
3264 Array<float, 4> clipData = {-0.5f, -0.5f, float(texBounds.width) + 0.5f,
3265 float(texBounds.height) + 0.5f};
3266 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramClipBounds, clipData,
3267 mSolidProgramUniformState.mClipBounds);
3268 Array<float, 4> colorData = {1.0f, 1.0f, 1.0f, 1.0f};
3269 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramColor, colorData,
3270 mSolidProgramUniformState.mColor);
3271 Array<float, 6> xformData = {1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f};
3272 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramTransform, xformData,
3273 mSolidProgramUniformState.mTransform);
3275 // Ensure the current clip mask is ignored.
3276 RefPtr<WebGLTexture> prevClipMask = mLastClipMask;
3277 SetNoClipMask();
3279 // Draw the mask using the supplied path vertex range.
3280 DrawTriangles(aVertexRange);
3282 // Restore the previous framebuffer state.
3283 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mCurrentTarget->mFramebuffer);
3284 mWebgl->Viewport(0, 0, mViewportSize.width, mViewportSize.height);
3285 if (prevClipMask) {
3286 SetClipMask(prevClipMask);
3289 return handle.forget();
3292 bool SharedContextWebgl::DrawPathAccel(
3293 const Path* aPath, const Pattern& aPattern, const DrawOptions& aOptions,
3294 const StrokeOptions* aStrokeOptions, bool aAllowStrokeAlpha,
3295 const ShadowOptions* aShadow, bool aCacheable, const Matrix* aPathXform) {
3296 // Get the transformed bounds for the path and conservatively check if the
3297 // bounds overlap the canvas.
3298 const PathSkia* pathSkia = static_cast<const PathSkia*>(aPath);
3299 const Matrix& currentTransform = mCurrentTarget->GetTransform();
3300 Matrix pathXform = currentTransform;
3301 // If there is a path-specific transform that shouldn't be applied to the
3302 // pattern, then generate a matrix that should only be used with the Skia
3303 // path.
3304 if (aPathXform) {
3305 pathXform.PreMultiply(*aPathXform);
3307 Rect bounds = pathSkia->GetFastBounds(pathXform, aStrokeOptions);
3308 // If the path is empty, then there is nothing to draw.
3309 if (bounds.IsEmpty()) {
3310 return true;
3312 IntRect viewport(IntPoint(), mViewportSize);
3313 if (aShadow) {
3314 // Inflate the bounds to account for the blur radius.
3315 bounds += aShadow->mOffset;
3316 int32_t blurRadius = aShadow->BlurRadius();
3317 bounds.Inflate(blurRadius);
3318 viewport.Inflate(blurRadius);
3320 Point realOrigin = bounds.TopLeft();
3321 if (aCacheable) {
3322 // Quantize the path origin to increase the reuse of cache entries.
3323 bounds.Scale(4.0f);
3324 bounds.Round();
3325 bounds.Scale(0.25f);
3327 Point quantizedOrigin = bounds.TopLeft();
3328 // If the path doesn't intersect the viewport, then there is nothing to draw.
3329 IntRect intBounds = RoundedOut(bounds).Intersect(viewport);
3330 if (intBounds.IsEmpty()) {
3331 return true;
3333 // Nudge the bounds to account for the quantization rounding.
3334 Rect quantBounds = Rect(intBounds) + (realOrigin - quantizedOrigin);
3335 // If the pattern is a solid color, then this will be used along with a path
3336 // mask to render the path, as opposed to baking the pattern into the cached
3337 // path texture.
3338 Maybe<DeviceColor> color =
3339 aOptions.mCompositionOp == CompositionOp::OP_CLEAR
3340 ? Some(DeviceColor(1, 1, 1, 1))
3341 : (aPattern.GetType() == PatternType::COLOR
3342 ? Some(static_cast<const ColorPattern&>(aPattern).mColor)
3343 : Nothing());
3344 // Look for an existing path cache entry, if possible, or otherwise create
3345 // one. If the draw request is not cacheable, then don't create an entry.
3346 RefPtr<PathCacheEntry> entry;
3347 RefPtr<TextureHandle> handle;
3348 if (aCacheable) {
3349 if (!mPathCache) {
3350 mPathCache = MakeUnique<PathCache>();
3352 // Use a quantized, relative (to its bounds origin) version of the path as
3353 // a cache key to help limit cache bloat.
3354 Maybe<QuantizedPath> qp = GenerateQuantizedPath(
3355 mWGRPathBuilder, pathSkia->GetPath(), quantBounds, pathXform);
3356 if (!qp) {
3357 return false;
3359 entry = mPathCache->FindOrInsertEntry(
3360 std::move(*qp), color ? nullptr : &aPattern, aStrokeOptions,
3361 currentTransform, intBounds, quantizedOrigin,
3362 aShadow ? aShadow->mSigma : -1.0f);
3363 if (!entry) {
3364 return false;
3366 handle = entry->GetHandle();
3369 // If there is a shadow, it needs to draw with the shadow color rather than
3370 // the path color.
3371 Maybe<DeviceColor> shadowColor = color;
3372 if (aShadow && aOptions.mCompositionOp != CompositionOp::OP_CLEAR) {
3373 shadowColor = Some(aShadow->mColor);
3374 if (color) {
3375 shadowColor->a *= color->a;
3378 SamplingFilter filter =
3379 aShadow ? SamplingFilter::GOOD : GetSamplingFilter(aPattern);
3380 if (handle && handle->IsValid()) {
3381 // If the entry has a valid texture handle still, use it. However, the
3382 // entry texture is assumed to be located relative to its previous bounds.
3383 // We need to offset the pattern by the difference between its new unclipped
3384 // origin and its previous previous unclipped origin. Then when we finally
3385 // draw a rectangle at the expected new bounds, it will overlap the portion
3386 // of the old entry texture we actually need to sample from.
3387 Point offset =
3388 (realOrigin - entry->GetOrigin()) + entry->GetBounds().TopLeft();
3389 SurfacePattern pathPattern(nullptr, ExtendMode::CLAMP,
3390 Matrix::Translation(offset), filter);
3391 return DrawRectAccel(quantBounds, pathPattern, aOptions, shadowColor,
3392 &handle, false, true, true);
3395 if (mPathVertexCapacity > 0 && !handle && entry && !aShadow &&
3396 aOptions.mAntialiasMode != AntialiasMode::NONE &&
3397 SupportsPattern(aPattern) &&
3398 entry->GetPath().mPath.num_types <= mPathMaxComplexity) {
3399 if (entry->GetVertexRange().IsValid()) {
3400 // If there is a valid cached vertex data in the path vertex buffer, then
3401 // just draw that. We must draw at integer pixel boundaries (using
3402 // intBounds instead of quantBounds) due to WGR's reliance on pixel center
3403 // location.
3404 mCurrentTarget->mProfile.OnCacheHit();
3405 return DrawRectAccel(Rect(intBounds.TopLeft(), Size(1, 1)), aPattern,
3406 aOptions, Nothing(), nullptr, false, true, true,
3407 false, nullptr, &entry->GetVertexRange());
3410 // printf_stderr("Generating... verbs %d, points %d\n",
3411 // int(pathSkia->GetPath().countVerbs()),
3412 // int(pathSkia->GetPath().countPoints()));
3413 WGR::OutputVertex* outputBuffer = nullptr;
3414 size_t outputBufferCapacity = 0;
3415 if (mWGROutputBuffer) {
3416 outputBuffer = mWGROutputBuffer.get();
3417 outputBufferCapacity = mPathVertexCapacity / sizeof(WGR::OutputVertex);
3419 Maybe<WGR::VertexBuffer> wgrVB;
3420 Maybe<AAStroke::VertexBuffer> strokeVB;
3421 if (!aStrokeOptions) {
3422 if (aPath == mUnitCirclePath) {
3423 auto scaleFactors = pathXform.ScaleFactors();
3424 if (scaleFactors.AreScalesSame()) {
3425 Point center = pathXform.GetTranslation() - quantBounds.TopLeft();
3426 float radius = scaleFactors.xScale;
3427 AAStroke::VertexBuffer vb = AAStroke::aa_stroke_filled_circle(
3428 center.x, center.y, radius, (AAStroke::OutputVertex*)outputBuffer,
3429 outputBufferCapacity);
3430 if (!vb.len || (outputBuffer && vb.len > outputBufferCapacity)) {
3431 AAStroke::aa_stroke_vertex_buffer_release(vb);
3432 } else {
3433 strokeVB = Some(vb);
3437 if (!strokeVB) {
3438 wgrVB = GeneratePathVertexBuffer(
3439 entry->GetPath(), IntRect(-intBounds.TopLeft(), mViewportSize),
3440 mRasterizationTruncates, outputBuffer, outputBufferCapacity);
3442 } else {
3443 if (mPathAAStroke &&
3444 SupportsAAStroke(aPattern, aOptions, *aStrokeOptions,
3445 aAllowStrokeAlpha) != AAStrokeMode::Unsupported) {
3446 auto scaleFactors = currentTransform.ScaleFactors();
3447 if (scaleFactors.AreScalesSame()) {
3448 strokeVB = GenerateStrokeVertexBuffer(
3449 entry->GetPath(), aStrokeOptions, scaleFactors.xScale,
3450 outputBuffer, outputBufferCapacity);
3453 if (!strokeVB && mPathWGRStroke) {
3454 // If stroking, then generate a path to fill the stroked region. This
3455 // path will need to be quantized again because it differs from the
3456 // path used for the cache entry, but this allows us to avoid
3457 // generating a fill path on a cache hit.
3458 Maybe<Rect> cullRect;
3459 Matrix invTransform = currentTransform;
3460 if (invTransform.Invert()) {
3461 // Transform the stroking clip rect from device space to local
3462 // space.
3463 Rect invRect = invTransform.TransformBounds(Rect(mClipRect));
3464 invRect.RoundOut();
3465 cullRect = Some(invRect);
3467 SkPath fillPath;
3468 if (pathSkia->GetFillPath(*aStrokeOptions, pathXform, fillPath,
3469 cullRect)) {
3470 // printf_stderr(" stroke fill... verbs %d, points %d\n",
3471 // int(fillPath.countVerbs()),
3472 // int(fillPath.countPoints()));
3473 if (Maybe<QuantizedPath> qp = GenerateQuantizedPath(
3474 mWGRPathBuilder, fillPath, quantBounds, pathXform)) {
3475 wgrVB = GeneratePathVertexBuffer(
3476 *qp, IntRect(-intBounds.TopLeft(), mViewportSize),
3477 mRasterizationTruncates, outputBuffer, outputBufferCapacity);
3482 if (wgrVB || strokeVB) {
3483 const uint8_t* vbData =
3484 wgrVB ? (const uint8_t*)wgrVB->data : (const uint8_t*)strokeVB->data;
3485 if (outputBuffer && !vbData) {
3486 vbData = (const uint8_t*)outputBuffer;
3488 size_t vbLen = wgrVB ? wgrVB->len : strokeVB->len;
3489 uint32_t vertexBytes = uint32_t(
3490 std::min(vbLen * sizeof(WGR::OutputVertex), size_t(UINT32_MAX)));
3491 // printf_stderr(" ... %d verts, %d bytes\n", int(vbLen),
3492 // int(vertexBytes));
3493 if (vertexBytes > mPathVertexCapacity - mPathVertexOffset &&
3494 vertexBytes <= mPathVertexCapacity - sizeof(kRectVertexData)) {
3495 // If the vertex data is too large to fit in the remaining path vertex
3496 // buffer, then orphan the contents of the vertex buffer to make room
3497 // for it.
3498 if (mPathCache) {
3499 mPathCache->ClearVertexRanges();
3501 ResetPathVertexBuffer(false);
3503 if (vertexBytes <= mPathVertexCapacity - mPathVertexOffset) {
3504 // If there is actually room to fit the vertex data in the vertex buffer
3505 // after orphaning as necessary, then upload the data to the next
3506 // available offset in the buffer.
3507 PathVertexRange vertexRange(
3508 uint32_t(mPathVertexOffset / sizeof(WGR::OutputVertex)),
3509 uint32_t(vbLen));
3510 // printf_stderr(" ... offset %d\n", mPathVertexOffset);
3511 // Normal glBufferSubData interleaved with draw calls causes performance
3512 // issues on Mali, so use our special unsynchronized version. This is
3513 // safe as we never update regions referenced by pending draw calls.
3514 mWebgl->BufferSubData(LOCAL_GL_ARRAY_BUFFER, mPathVertexOffset,
3515 vertexBytes, vbData,
3516 /* unsynchronized */ true);
3517 mPathVertexOffset += vertexBytes;
3518 if (wgrVB) {
3519 WGR::wgr_vertex_buffer_release(wgrVB.ref());
3520 } else {
3521 AAStroke::aa_stroke_vertex_buffer_release(strokeVB.ref());
3523 if (strokeVB && aStrokeOptions &&
3524 SupportsAAStroke(aPattern, aOptions, *aStrokeOptions,
3525 aAllowStrokeAlpha) == AAStrokeMode::Mask) {
3526 // Attempt to generate a stroke mask for path.
3527 if (RefPtr<TextureHandle> handle =
3528 DrawStrokeMask(vertexRange, intBounds.Size())) {
3529 // Finally, draw the rendered stroke mask.
3530 if (entry) {
3531 entry->Link(handle);
3533 mCurrentTarget->mProfile.OnCacheMiss();
3534 SurfacePattern maskPattern(
3535 nullptr, ExtendMode::CLAMP,
3536 Matrix::Translation(quantBounds.TopLeft()),
3537 SamplingFilter::GOOD);
3538 return DrawRectAccel(quantBounds, maskPattern, aOptions, color,
3539 &handle, false, true, true);
3541 } else {
3542 // Remember the vertex range in the cache entry so that it can be
3543 // reused later.
3544 if (entry) {
3545 entry->SetVertexRange(vertexRange);
3548 // Finally, draw the uploaded vertex data.
3549 mCurrentTarget->mProfile.OnCacheMiss();
3550 return DrawRectAccel(Rect(intBounds.TopLeft(), Size(1, 1)), aPattern,
3551 aOptions, Nothing(), nullptr, false, true, true,
3552 false, nullptr, &vertexRange);
3554 } else {
3555 if (wgrVB) {
3556 WGR::wgr_vertex_buffer_release(wgrVB.ref());
3557 } else {
3558 AAStroke::aa_stroke_vertex_buffer_release(strokeVB.ref());
3561 // If we failed to draw the vertex data for some reason, then fall through
3562 // to the texture rasterization path.
3566 // If a stroke path covers too much screen area, it is likely that most is
3567 // empty space in the interior. This usually imposes too high a cost versus
3568 // just rasterizing without acceleration. Note that AA-Stroke generally
3569 // produces more acceptable amounts of geometry for larger paths, so we do
3570 // this heuristic after we attempt AA-Stroke.
3571 if (aStrokeOptions &&
3572 intBounds.width * intBounds.height >
3573 (mViewportSize.width / 2) * (mViewportSize.height / 2)) {
3574 return false;
3577 // If there isn't a valid texture handle, then we need to rasterize the
3578 // path in a software canvas and upload this to a texture. Solid color
3579 // patterns will be rendered as a path mask that can then be modulated
3580 // with any color. Other pattern types have to rasterize the pattern
3581 // directly into the cached texture.
3582 handle = nullptr;
3583 RefPtr<DrawTargetSkia> pathDT = new DrawTargetSkia;
3584 if (pathDT->Init(intBounds.Size(), color || aShadow
3585 ? SurfaceFormat::A8
3586 : SurfaceFormat::B8G8R8A8)) {
3587 Point offset = -quantBounds.TopLeft();
3588 if (aShadow) {
3589 // Ensure the the shadow is drawn at the requested offset
3590 offset += aShadow->mOffset;
3592 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER,
3593 aOptions.mAntialiasMode);
3594 static const ColorPattern maskPattern(DeviceColor(1.0f, 1.0f, 1.0f, 1.0f));
3595 const Pattern& cachePattern = color ? maskPattern : aPattern;
3596 // If the source pattern is a DrawTargetWebgl snapshot, we may shift
3597 // targets when drawing the path, so back up the old target.
3598 DrawTargetWebgl* oldTarget = mCurrentTarget;
3600 RefPtr<const Path> path;
3601 if (color || !aPathXform) {
3602 // If the pattern is transform invariant or there is no pathXform, then
3603 // it is safe to use the path directly.
3604 path = aPath;
3605 pathDT->SetTransform(pathXform * Matrix::Translation(offset));
3606 } else {
3607 // If there is a pathXform, then pre-apply that to the path to avoid
3608 // altering the pattern.
3609 RefPtr<PathBuilder> builder =
3610 aPath->TransformedCopyToBuilder(*aPathXform);
3611 path = builder->Finish();
3612 pathDT->SetTransform(currentTransform * Matrix::Translation(offset));
3614 if (aStrokeOptions) {
3615 pathDT->Stroke(path, cachePattern, *aStrokeOptions, drawOptions);
3616 } else {
3617 pathDT->Fill(path, cachePattern, drawOptions);
3620 if (aShadow && aShadow->mSigma > 0.0f) {
3621 // Blur the shadow if required.
3622 uint8_t* data = nullptr;
3623 IntSize size;
3624 int32_t stride = 0;
3625 SurfaceFormat format = SurfaceFormat::UNKNOWN;
3626 if (pathDT->LockBits(&data, &size, &stride, &format)) {
3627 AlphaBoxBlur blur(Rect(pathDT->GetRect()), stride, aShadow->mSigma,
3628 aShadow->mSigma);
3629 blur.Blur(data);
3630 pathDT->ReleaseBits(data);
3633 RefPtr<SourceSurface> pathSurface = pathDT->Snapshot();
3634 if (pathSurface) {
3635 // If the target changed, try to restore it.
3636 if (mCurrentTarget != oldTarget && !oldTarget->PrepareContext()) {
3637 return false;
3639 SurfacePattern pathPattern(pathSurface, ExtendMode::CLAMP,
3640 Matrix::Translation(quantBounds.TopLeft()),
3641 filter);
3642 // Try and upload the rasterized path to a texture. If there is a
3643 // valid texture handle after this, then link it to the entry.
3644 // Otherwise, we might have to fall back to software drawing the
3645 // path, so unlink it from the entry.
3646 if (DrawRectAccel(quantBounds, pathPattern, aOptions, shadowColor,
3647 &handle, false, true) &&
3648 handle) {
3649 if (entry) {
3650 entry->Link(handle);
3652 } else if (entry) {
3653 entry->Unlink();
3655 return true;
3659 return false;
3662 void DrawTargetWebgl::DrawPath(const Path* aPath, const Pattern& aPattern,
3663 const DrawOptions& aOptions,
3664 const StrokeOptions* aStrokeOptions,
3665 bool aAllowStrokeAlpha) {
3666 // If there is a WebGL context, then try to cache the path to avoid slow
3667 // fallbacks.
3668 if (ShouldAccelPath(aOptions, aStrokeOptions) &&
3669 mSharedContext->DrawPathAccel(aPath, aPattern, aOptions, aStrokeOptions,
3670 aAllowStrokeAlpha)) {
3671 return;
3674 // There was no path cache entry available to use, so fall back to drawing the
3675 // path with Skia.
3676 MarkSkiaChanged(aOptions);
3677 if (aStrokeOptions) {
3678 mSkia->Stroke(aPath, aPattern, *aStrokeOptions, aOptions);
3679 } else {
3680 mSkia->Fill(aPath, aPattern, aOptions);
3684 // DrawCircleAccel is a more specialized version of DrawPathAccel that attempts
3685 // to cache a unit circle.
3686 bool SharedContextWebgl::DrawCircleAccel(const Point& aCenter, float aRadius,
3687 const Pattern& aPattern,
3688 const DrawOptions& aOptions,
3689 const StrokeOptions* aStrokeOptions) {
3690 // Cache a unit circle and transform it to avoid creating a path repeatedly.
3691 if (!mUnitCirclePath) {
3692 mUnitCirclePath = MakePathForCircle(*mCurrentTarget, Point(0, 0), 1);
3694 // Scale and translate the circle to the desired shape.
3695 Matrix circleXform(aRadius, 0, 0, aRadius, aCenter.x, aCenter.y);
3696 return DrawPathAccel(mUnitCirclePath, aPattern, aOptions, aStrokeOptions,
3697 true, nullptr, true, &circleXform);
3700 void DrawTargetWebgl::DrawCircle(const Point& aOrigin, float aRadius,
3701 const Pattern& aPattern,
3702 const DrawOptions& aOptions,
3703 const StrokeOptions* aStrokeOptions) {
3704 if (ShouldAccelPath(aOptions, aStrokeOptions) &&
3705 mSharedContext->DrawCircleAccel(aOrigin, aRadius, aPattern, aOptions,
3706 aStrokeOptions)) {
3707 return;
3710 MarkSkiaChanged(aOptions);
3711 if (aStrokeOptions) {
3712 mSkia->StrokeCircle(aOrigin, aRadius, aPattern, *aStrokeOptions, aOptions);
3713 } else {
3714 mSkia->FillCircle(aOrigin, aRadius, aPattern, aOptions);
3718 void DrawTargetWebgl::DrawSurface(SourceSurface* aSurface, const Rect& aDest,
3719 const Rect& aSource,
3720 const DrawSurfaceOptions& aSurfOptions,
3721 const DrawOptions& aOptions) {
3722 Matrix matrix = Matrix::Scaling(aDest.width / aSource.width,
3723 aDest.height / aSource.height);
3724 matrix.PreTranslate(-aSource.x, -aSource.y);
3725 matrix.PostTranslate(aDest.x, aDest.y);
3726 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, matrix,
3727 aSurfOptions.mSamplingFilter);
3728 DrawRect(aDest, pattern, aOptions);
3731 void DrawTargetWebgl::Mask(const Pattern& aSource, const Pattern& aMask,
3732 const DrawOptions& aOptions) {
3733 if (!SupportsDrawOptions(aOptions) ||
3734 aMask.GetType() != PatternType::SURFACE ||
3735 aSource.GetType() != PatternType::COLOR) {
3736 MarkSkiaChanged(aOptions);
3737 mSkia->Mask(aSource, aMask, aOptions);
3738 return;
3740 auto sourceColor = static_cast<const ColorPattern&>(aSource).mColor;
3741 auto maskPattern = static_cast<const SurfacePattern&>(aMask);
3742 DrawRect(Rect(IntRect(IntPoint(), maskPattern.mSurface->GetSize())),
3743 maskPattern, aOptions, Some(sourceColor));
3746 void DrawTargetWebgl::MaskSurface(const Pattern& aSource, SourceSurface* aMask,
3747 Point aOffset, const DrawOptions& aOptions) {
3748 if (!SupportsDrawOptions(aOptions) ||
3749 aSource.GetType() != PatternType::COLOR) {
3750 MarkSkiaChanged(aOptions);
3751 mSkia->MaskSurface(aSource, aMask, aOffset, aOptions);
3752 } else {
3753 auto sourceColor = static_cast<const ColorPattern&>(aSource).mColor;
3754 SurfacePattern pattern(aMask, ExtendMode::CLAMP,
3755 Matrix::Translation(aOffset));
3756 DrawRect(Rect(aOffset, Size(aMask->GetSize())), pattern, aOptions,
3757 Some(sourceColor));
3761 // Extract the surface's alpha values into an A8 surface.
3762 static already_AddRefed<DataSourceSurface> ExtractAlpha(SourceSurface* aSurface,
3763 bool aAllowSubpixelAA) {
3764 RefPtr<DataSourceSurface> surfaceData = aSurface->GetDataSurface();
3765 if (!surfaceData) {
3766 return nullptr;
3768 DataSourceSurface::ScopedMap srcMap(surfaceData, DataSourceSurface::READ);
3769 if (!srcMap.IsMapped()) {
3770 return nullptr;
3772 IntSize size = surfaceData->GetSize();
3773 RefPtr<DataSourceSurface> alpha =
3774 Factory::CreateDataSourceSurface(size, SurfaceFormat::A8, false);
3775 if (!alpha) {
3776 return nullptr;
3778 DataSourceSurface::ScopedMap dstMap(alpha, DataSourceSurface::WRITE);
3779 if (!dstMap.IsMapped()) {
3780 return nullptr;
3782 // For subpixel masks, ignore the alpha and instead sample one of the color
3783 // channels as if they were alpha.
3784 SwizzleData(
3785 srcMap.GetData(), srcMap.GetStride(),
3786 aAllowSubpixelAA ? SurfaceFormat::A8R8G8B8 : surfaceData->GetFormat(),
3787 dstMap.GetData(), dstMap.GetStride(), SurfaceFormat::A8, size);
3788 return alpha.forget();
3791 void DrawTargetWebgl::DrawShadow(const Path* aPath, const Pattern& aPattern,
3792 const ShadowOptions& aShadow,
3793 const DrawOptions& aOptions,
3794 const StrokeOptions* aStrokeOptions) {
3795 if (!aPath || aPath->GetBackendType() != BackendType::SKIA) {
3796 return;
3799 // If there is a WebGL context, then try to cache the path to avoid slow
3800 // fallbacks.
3801 if (ShouldAccelPath(aOptions, aStrokeOptions) &&
3802 mSharedContext->DrawPathAccel(aPath, aPattern, aOptions, aStrokeOptions,
3803 false, &aShadow)) {
3804 return;
3807 // There was no path cache entry available to use, so fall back to drawing the
3808 // path with Skia.
3809 MarkSkiaChanged(aOptions);
3810 mSkia->DrawShadow(aPath, aPattern, aShadow, aOptions, aStrokeOptions);
3813 void DrawTargetWebgl::DrawSurfaceWithShadow(SourceSurface* aSurface,
3814 const Point& aDest,
3815 const ShadowOptions& aShadow,
3816 CompositionOp aOperator) {
3817 DrawOptions options(1.0f, aOperator);
3818 if (ShouldAccelPath(options, nullptr)) {
3819 SurfacePattern pattern(aSurface, ExtendMode::CLAMP,
3820 Matrix::Translation(aDest));
3821 SkPath skiaPath;
3822 skiaPath.addRect(RectToSkRect(Rect(aSurface->GetRect()) + aDest));
3823 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
3824 AutoRestoreTransform restore(this);
3825 SetTransform(Matrix());
3826 if (mSharedContext->DrawPathAccel(path, pattern, options, nullptr, false,
3827 &aShadow, false)) {
3828 DrawRect(Rect(aSurface->GetRect()) + aDest, pattern, options);
3829 return;
3833 MarkSkiaChanged(options);
3834 mSkia->DrawSurfaceWithShadow(aSurface, aDest, aShadow, aOperator);
3837 already_AddRefed<PathBuilder> DrawTargetWebgl::CreatePathBuilder(
3838 FillRule aFillRule) const {
3839 return mSkia->CreatePathBuilder(aFillRule);
3842 void DrawTargetWebgl::SetTransform(const Matrix& aTransform) {
3843 DrawTarget::SetTransform(aTransform);
3844 mSkia->SetTransform(aTransform);
3847 void DrawTargetWebgl::StrokeRect(const Rect& aRect, const Pattern& aPattern,
3848 const StrokeOptions& aStrokeOptions,
3849 const DrawOptions& aOptions) {
3850 if (!mWebglValid) {
3851 MarkSkiaChanged(aOptions);
3852 mSkia->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions);
3853 } else {
3854 // If the stroke options are unsupported, then transform the rect to a path
3855 // so it can be cached.
3856 SkPath skiaPath;
3857 skiaPath.addRect(RectToSkRect(aRect));
3858 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
3859 DrawPath(path, aPattern, aOptions, &aStrokeOptions, true);
3863 static inline bool IsThinLine(const Matrix& aTransform,
3864 const StrokeOptions& aStrokeOptions) {
3865 auto scale = aTransform.ScaleFactors();
3866 return std::max(scale.xScale, scale.yScale) * aStrokeOptions.mLineWidth <= 1;
3869 bool DrawTargetWebgl::StrokeLineAccel(const Point& aStart, const Point& aEnd,
3870 const Pattern& aPattern,
3871 const StrokeOptions& aStrokeOptions,
3872 const DrawOptions& aOptions,
3873 bool aClosed) {
3874 // Approximating a wide line as a rectangle works only with certain cap styles
3875 // in the general case (butt or square). However, if the line width is
3876 // sufficiently thin, we can either ignore the round cap (or treat it like
3877 // square for zero-length lines) without causing objectionable artifacts.
3878 // Lines may sometimes be used in closed paths that immediately reverse back,
3879 // in which case we need to use mLineJoin instead of mLineCap to determine the
3880 // actual cap used.
3881 CapStyle capStyle =
3882 aClosed ? (aStrokeOptions.mLineJoin == JoinStyle::ROUND ? CapStyle::ROUND
3883 : CapStyle::BUTT)
3884 : aStrokeOptions.mLineCap;
3885 if (mWebglValid && SupportsPattern(aPattern) &&
3886 (capStyle != CapStyle::ROUND ||
3887 IsThinLine(GetTransform(), aStrokeOptions)) &&
3888 aStrokeOptions.mDashPattern == nullptr && aStrokeOptions.mLineWidth > 0) {
3889 // Treat the line as a rectangle whose center-line is the supplied line and
3890 // for which the height is the supplied line width. Generate a matrix that
3891 // maps the X axis to the orientation of the line and the Y axis to the
3892 // normal vector to the line. This only works if the line caps are squared,
3893 // as rounded rectangles are currently not supported for round line caps.
3894 Point start = aStart;
3895 Point dirX = aEnd - aStart;
3896 Point dirY;
3897 float dirLen = dirX.Length();
3898 float scale = aStrokeOptions.mLineWidth;
3899 if (dirLen == 0.0f) {
3900 // If the line is zero-length, then only a cap is rendered.
3901 switch (capStyle) {
3902 case CapStyle::BUTT:
3903 // The cap doesn't extend beyond the line so nothing is drawn.
3904 return true;
3905 case CapStyle::ROUND:
3906 case CapStyle::SQUARE:
3907 // Draw a unit square centered at the single point.
3908 dirX = Point(scale, 0.0f);
3909 dirY = Point(0.0f, scale);
3910 // Offset the start by half a unit.
3911 start.x -= 0.5f * scale;
3912 break;
3914 } else {
3915 // Make the scale map to a single unit length.
3916 scale /= dirLen;
3917 dirY = Point(-dirX.y, dirX.x) * scale;
3918 if (capStyle == CapStyle::SQUARE) {
3919 // Offset the start by half a unit.
3920 start -= (dirX * scale) * 0.5f;
3921 // Ensure the extent also accounts for the start and end cap.
3922 dirX += dirX * scale;
3925 Matrix lineXform(dirX.x, dirX.y, dirY.x, dirY.y, start.x - 0.5f * dirY.x,
3926 start.y - 0.5f * dirY.y);
3927 if (PrepareContext() &&
3928 mSharedContext->DrawRectAccel(Rect(0, 0, 1, 1), aPattern, aOptions,
3929 Nothing(), nullptr, true, true, true,
3930 false, nullptr, nullptr, &lineXform)) {
3931 return true;
3934 return false;
3937 void DrawTargetWebgl::StrokeLine(const Point& aStart, const Point& aEnd,
3938 const Pattern& aPattern,
3939 const StrokeOptions& aStrokeOptions,
3940 const DrawOptions& aOptions) {
3941 if (!mWebglValid) {
3942 MarkSkiaChanged(aOptions);
3943 mSkia->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions);
3944 } else if (!StrokeLineAccel(aStart, aEnd, aPattern, aStrokeOptions,
3945 aOptions)) {
3946 // If the stroke options are unsupported, then transform the line to a path
3947 // so it can be cached.
3948 SkPath skiaPath;
3949 skiaPath.moveTo(PointToSkPoint(aStart));
3950 skiaPath.lineTo(PointToSkPoint(aEnd));
3951 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
3952 DrawPath(path, aPattern, aOptions, &aStrokeOptions, true);
3956 void DrawTargetWebgl::Stroke(const Path* aPath, const Pattern& aPattern,
3957 const StrokeOptions& aStrokeOptions,
3958 const DrawOptions& aOptions) {
3959 if (!aPath || aPath->GetBackendType() != BackendType::SKIA) {
3960 return;
3962 const auto& skiaPath = static_cast<const PathSkia*>(aPath)->GetPath();
3963 if (!mWebglValid) {
3964 MarkSkiaChanged(aOptions);
3965 mSkia->Stroke(aPath, aPattern, aStrokeOptions, aOptions);
3966 return;
3969 // Avoid using Skia's isLine here because some paths erroneously include a
3970 // closePath at the end, causing isLine to not detect the line. In that case
3971 // we just draw a line in reverse right over the original line.
3972 int numVerbs = skiaPath.countVerbs();
3973 bool allowStrokeAlpha = false;
3974 if (numVerbs >= 2 && numVerbs <= 3) {
3975 uint8_t verbs[3];
3976 skiaPath.getVerbs(verbs, numVerbs);
3977 if (verbs[0] == SkPath::kMove_Verb && verbs[1] == SkPath::kLine_Verb &&
3978 (numVerbs < 3 || verbs[2] == SkPath::kClose_Verb)) {
3979 bool closed = numVerbs >= 3;
3980 Point start = SkPointToPoint(skiaPath.getPoint(0));
3981 Point end = SkPointToPoint(skiaPath.getPoint(1));
3982 if (StrokeLineAccel(start, end, aPattern, aStrokeOptions, aOptions,
3983 closed)) {
3984 if (closed) {
3985 StrokeLineAccel(end, start, aPattern, aStrokeOptions, aOptions, true);
3987 return;
3989 // If accelerated line drawing failed, just treat it as a path.
3990 allowStrokeAlpha = true;
3994 DrawPath(aPath, aPattern, aOptions, &aStrokeOptions, allowStrokeAlpha);
3997 void DrawTargetWebgl::StrokeCircle(const Point& aOrigin, float aRadius,
3998 const Pattern& aPattern,
3999 const StrokeOptions& aStrokeOptions,
4000 const DrawOptions& aOptions) {
4001 DrawCircle(aOrigin, aRadius, aPattern, aOptions, &aStrokeOptions);
4004 bool DrawTargetWebgl::ShouldUseSubpixelAA(ScaledFont* aFont,
4005 const DrawOptions& aOptions) {
4006 AntialiasMode aaMode = aFont->GetDefaultAAMode();
4007 if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
4008 aaMode = aOptions.mAntialiasMode;
4010 return GetPermitSubpixelAA() &&
4011 (aaMode == AntialiasMode::DEFAULT ||
4012 aaMode == AntialiasMode::SUBPIXEL) &&
4013 aOptions.mCompositionOp == CompositionOp::OP_OVER;
4016 void DrawTargetWebgl::StrokeGlyphs(ScaledFont* aFont,
4017 const GlyphBuffer& aBuffer,
4018 const Pattern& aPattern,
4019 const StrokeOptions& aStrokeOptions,
4020 const DrawOptions& aOptions) {
4021 if (!aFont || !aBuffer.mNumGlyphs) {
4022 return;
4025 bool useSubpixelAA = ShouldUseSubpixelAA(aFont, aOptions);
4027 if (mWebglValid && SupportsDrawOptions(aOptions) &&
4028 aPattern.GetType() == PatternType::COLOR && PrepareContext() &&
4029 mSharedContext->DrawGlyphsAccel(aFont, aBuffer, aPattern, aOptions,
4030 &aStrokeOptions, useSubpixelAA)) {
4031 return;
4034 if (useSubpixelAA) {
4035 // Subpixel AA does not support layering because the subpixel masks can't
4036 // blend with the over op.
4037 MarkSkiaChanged();
4038 } else {
4039 MarkSkiaChanged(aOptions);
4041 mSkia->StrokeGlyphs(aFont, aBuffer, aPattern, aStrokeOptions, aOptions);
4044 // Depending on whether we enable subpixel position for a given font, Skia may
4045 // round transformed coordinates differently on each axis. By default, text is
4046 // subpixel quantized horizontally and snapped to a whole integer vertical
4047 // baseline. Axis-flip transforms instead snap to horizontal boundaries while
4048 // subpixel quantizing along the vertical. For other types of transforms, Skia
4049 // just applies subpixel quantization to both axes.
4050 // We must duplicate the amount of quantization Skia applies carefully as a
4051 // boundary value such as 0.49 may round to 0.5 with subpixel quantization,
4052 // but if Skia actually snapped it to a whole integer instead, it would round
4053 // down to 0. If a subsequent glyph with offset 0.51 came in, we might
4054 // mistakenly round it down to 0.5, whereas Skia would round it up to 1. Thus
4055 // we would alias 0.49 and 0.51 to the same cache entry, while Skia would
4056 // actually snap the offset to 0 or 1, depending, resulting in mismatched
4057 // hinting.
4058 static inline IntPoint QuantizeScale(ScaledFont* aFont,
4059 const Matrix& aTransform) {
4060 if (!aFont->UseSubpixelPosition()) {
4061 return {1, 1};
4063 if (aTransform._12 == 0) {
4064 // Glyphs are rendered subpixel horizontally, so snap vertically.
4065 return {4, 1};
4067 if (aTransform._11 == 0) {
4068 // Glyphs are rendered subpixel vertically, so snap horizontally.
4069 return {1, 4};
4071 // The transform isn't aligned, so don't snap.
4072 return {4, 4};
4075 // Skia only supports subpixel positioning to the nearest 1/4 fraction. It
4076 // would be wasteful to attempt to cache text runs with positioning that is
4077 // anymore precise than this. To prevent this cache bloat, we quantize the
4078 // transformed glyph positions to the nearest 1/4. The scaling factor for
4079 // the quantization is baked into the transform, so that if subpixel rounding
4080 // is used on a given axis, then the axis will be multiplied by 4 before
4081 // rounding. Since the quantized position is not used for rasterization, the
4082 // transform is safe to modify as such.
4083 static inline IntPoint QuantizePosition(const Matrix& aTransform,
4084 const IntPoint& aOffset,
4085 const Point& aPosition) {
4086 return RoundedToInt(aTransform.TransformPoint(aPosition)) - aOffset;
4089 // Get a quantized starting offset for the glyph buffer. We want this offset
4090 // to encapsulate the transform and buffer offset while still preserving the
4091 // relative subpixel positions of the glyphs this offset is subtracted from.
4092 static inline IntPoint QuantizeOffset(const Matrix& aTransform,
4093 const IntPoint& aQuantizeScale,
4094 const GlyphBuffer& aBuffer) {
4095 IntPoint offset =
4096 RoundedToInt(aTransform.TransformPoint(aBuffer.mGlyphs[0].mPosition));
4097 offset.x.value &= ~(aQuantizeScale.x.value - 1);
4098 offset.y.value &= ~(aQuantizeScale.y.value - 1);
4099 return offset;
4102 // Hashes a glyph buffer to a single hash value that can be used for quick
4103 // comparisons. Each glyph position is transformed and quantized before
4104 // hashing.
4105 HashNumber GlyphCacheEntry::HashGlyphs(const GlyphBuffer& aBuffer,
4106 const Matrix& aTransform,
4107 const IntPoint& aQuantizeScale) {
4108 HashNumber hash = 0;
4109 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer);
4110 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) {
4111 const Glyph& glyph = aBuffer.mGlyphs[i];
4112 hash = AddToHash(hash, glyph.mIndex);
4113 IntPoint pos = QuantizePosition(aTransform, offset, glyph.mPosition);
4114 hash = AddToHash(hash, pos.x);
4115 hash = AddToHash(hash, pos.y);
4117 return hash;
4120 // Determines if an existing glyph cache entry matches an incoming text run.
4121 inline bool GlyphCacheEntry::MatchesGlyphs(
4122 const GlyphBuffer& aBuffer, const DeviceColor& aColor,
4123 const Matrix& aTransform, const IntPoint& aQuantizeOffset,
4124 const IntPoint& aBoundsOffset, const IntRect& aClipRect, HashNumber aHash,
4125 const StrokeOptions* aStrokeOptions) {
4126 // First check if the hash matches to quickly reject the text run before any
4127 // more expensive checking. If it matches, then check if the color and
4128 // transform are the same.
4129 if (aHash != mHash || aBuffer.mNumGlyphs != mBuffer.mNumGlyphs ||
4130 aColor != mColor || !HasMatchingScale(aTransform, mTransform)) {
4131 return false;
4133 // Finally check if all glyphs and their quantized positions match.
4134 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) {
4135 const Glyph& dst = mBuffer.mGlyphs[i];
4136 const Glyph& src = aBuffer.mGlyphs[i];
4137 if (dst.mIndex != src.mIndex ||
4138 dst.mPosition != Point(QuantizePosition(aTransform, aQuantizeOffset,
4139 src.mPosition))) {
4140 return false;
4143 // Check that stroke options actually match.
4144 if (aStrokeOptions) {
4145 // If stroking, verify that the entry is also stroked with the same options.
4146 if (!(mStrokeOptions && *aStrokeOptions == *mStrokeOptions)) {
4147 return false;
4149 } else if (mStrokeOptions) {
4150 // If not stroking, check if the entry is stroked. If so, don't match.
4151 return false;
4153 // Verify that the full bounds, once translated and clipped, are equal to the
4154 // clipped bounds.
4155 return (mFullBounds + aBoundsOffset)
4156 .Intersect(aClipRect)
4157 .IsEqualEdges(GetBounds() + aBoundsOffset);
4160 GlyphCacheEntry::GlyphCacheEntry(const GlyphBuffer& aBuffer,
4161 const DeviceColor& aColor,
4162 const Matrix& aTransform,
4163 const IntPoint& aQuantizeScale,
4164 const IntRect& aBounds,
4165 const IntRect& aFullBounds, HashNumber aHash,
4166 StoredStrokeOptions* aStrokeOptions)
4167 : CacheEntryImpl<GlyphCacheEntry>(aTransform, aBounds, aHash),
4168 mColor(aColor),
4169 mFullBounds(aFullBounds),
4170 mStrokeOptions(aStrokeOptions) {
4171 // Store a copy of the glyph buffer with positions already quantized for fast
4172 // comparison later.
4173 Glyph* glyphs = new Glyph[aBuffer.mNumGlyphs];
4174 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer);
4175 // Make the bounds relative to the offset so we can add a new offset later.
4176 IntPoint boundsOffset(offset.x / aQuantizeScale.x,
4177 offset.y / aQuantizeScale.y);
4178 mBounds -= boundsOffset;
4179 mFullBounds -= boundsOffset;
4180 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) {
4181 Glyph& dst = glyphs[i];
4182 const Glyph& src = aBuffer.mGlyphs[i];
4183 dst.mIndex = src.mIndex;
4184 dst.mPosition = Point(QuantizePosition(aTransform, offset, src.mPosition));
4186 mBuffer.mGlyphs = glyphs;
4187 mBuffer.mNumGlyphs = aBuffer.mNumGlyphs;
4190 GlyphCacheEntry::~GlyphCacheEntry() { delete[] mBuffer.mGlyphs; }
4192 // Attempt to find a matching entry in the glyph cache. The caller should check
4193 // whether the contained texture handle is valid to determine if it will need to
4194 // render the text run or just reuse the cached texture.
4195 already_AddRefed<GlyphCacheEntry> GlyphCache::FindEntry(
4196 const GlyphBuffer& aBuffer, const DeviceColor& aColor,
4197 const Matrix& aTransform, const IntPoint& aQuantizeScale,
4198 const IntRect& aClipRect, HashNumber aHash,
4199 const StrokeOptions* aStrokeOptions) {
4200 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer);
4201 IntPoint boundsOffset(offset.x / aQuantizeScale.x,
4202 offset.y / aQuantizeScale.y);
4203 for (const RefPtr<GlyphCacheEntry>& entry : GetChain(aHash)) {
4204 if (entry->MatchesGlyphs(aBuffer, aColor, aTransform, offset, boundsOffset,
4205 aClipRect, aHash, aStrokeOptions)) {
4206 return do_AddRef(entry);
4209 return nullptr;
4212 // Insert a new entry in the glyph cache.
4213 already_AddRefed<GlyphCacheEntry> GlyphCache::InsertEntry(
4214 const GlyphBuffer& aBuffer, const DeviceColor& aColor,
4215 const Matrix& aTransform, const IntPoint& aQuantizeScale,
4216 const IntRect& aBounds, const IntRect& aFullBounds, HashNumber aHash,
4217 const StrokeOptions* aStrokeOptions) {
4218 StoredStrokeOptions* strokeOptions = nullptr;
4219 if (aStrokeOptions) {
4220 strokeOptions = aStrokeOptions->Clone();
4221 if (!strokeOptions) {
4222 return nullptr;
4225 RefPtr<GlyphCacheEntry> entry =
4226 new GlyphCacheEntry(aBuffer, aColor, aTransform, aQuantizeScale, aBounds,
4227 aFullBounds, aHash, strokeOptions);
4228 Insert(entry);
4229 return entry.forget();
4232 GlyphCache::GlyphCache(ScaledFont* aFont) : mFont(aFont) {}
4234 static void ReleaseGlyphCache(void* aPtr) {
4235 delete static_cast<GlyphCache*>(aPtr);
4238 void DrawTargetWebgl::SetPermitSubpixelAA(bool aPermitSubpixelAA) {
4239 DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA);
4240 mSkia->SetPermitSubpixelAA(aPermitSubpixelAA);
4243 // Check for any color glyphs contained within a rasterized BGRA8 text result.
4244 static bool CheckForColorGlyphs(const RefPtr<SourceSurface>& aSurface) {
4245 if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) {
4246 return false;
4248 RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface();
4249 if (!dataSurf) {
4250 return true;
4252 DataSourceSurface::ScopedMap map(dataSurf, DataSourceSurface::READ);
4253 if (!map.IsMapped()) {
4254 return true;
4256 IntSize size = dataSurf->GetSize();
4257 const uint8_t* data = map.GetData();
4258 int32_t stride = map.GetStride();
4259 for (int y = 0; y < size.height; y++) {
4260 const uint32_t* x = (const uint32_t*)data;
4261 const uint32_t* end = x + size.width;
4262 for (; x < end; x++) {
4263 // Verify if all components are the same as for premultiplied grayscale.
4264 uint32_t color = *x;
4265 uint32_t gray = color & 0xFF;
4266 gray |= gray << 8;
4267 gray |= gray << 16;
4268 if (color != gray) return true;
4270 data += stride;
4272 return false;
4275 // Draws glyphs to the WebGL target by trying to generate a cached texture for
4276 // the text run that can be subsequently reused to quickly render the text run
4277 // without using any software surfaces.
4278 bool SharedContextWebgl::DrawGlyphsAccel(ScaledFont* aFont,
4279 const GlyphBuffer& aBuffer,
4280 const Pattern& aPattern,
4281 const DrawOptions& aOptions,
4282 const StrokeOptions* aStrokeOptions,
4283 bool aUseSubpixelAA) {
4284 // Whether the font may use bitmaps. If so, we need to render the glyphs with
4285 // color as grayscale bitmaps will use the color while color emoji will not,
4286 // with no easy way to know ahead of time. We currently have to check the
4287 // rasterized result to see if there are any color glyphs. To render subpixel
4288 // masks, we need to know that the rasterized result actually represents a
4289 // subpixel mask rather than try to interpret it as a normal RGBA result such
4290 // as for color emoji.
4291 bool useBitmaps = !aStrokeOptions && aFont->MayUseBitmaps() &&
4292 aOptions.mCompositionOp != CompositionOp::OP_CLEAR;
4294 // Look for an existing glyph cache on the font. If not there, create it.
4295 GlyphCache* cache =
4296 static_cast<GlyphCache*>(aFont->GetUserData(&mGlyphCacheKey));
4297 if (!cache) {
4298 cache = new GlyphCache(aFont);
4299 aFont->AddUserData(&mGlyphCacheKey, cache, ReleaseGlyphCache);
4300 mGlyphCaches.insertFront(cache);
4302 // Hash the incoming text run and looking for a matching entry.
4303 DeviceColor color = aOptions.mCompositionOp == CompositionOp::OP_CLEAR
4304 ? DeviceColor(1, 1, 1, 1)
4305 : static_cast<const ColorPattern&>(aPattern).mColor;
4306 #ifdef XP_MACOSX
4307 // On macOS, depending on whether the text is classified as light-on-dark or
4308 // dark-on-light, we may end up with different amounts of dilation applied, so
4309 // we can't use the same mask in the two circumstances, or the glyphs will be
4310 // dilated incorrectly.
4311 bool lightOnDark =
4312 useBitmaps || (color.r >= 0.33f && color.g >= 0.33f && color.b >= 0.33f &&
4313 color.r + color.g + color.b >= 2.0f);
4314 #else
4315 // On other platforms, we assume no color-dependent dilation.
4316 const bool lightOnDark = true;
4317 #endif
4318 // If the font has bitmaps, use the color directly. Otherwise, the texture
4319 // will hold a grayscale mask, so encode the key's subpixel and light-or-dark
4320 // state in the color.
4321 const Matrix& currentTransform = mCurrentTarget->GetTransform();
4322 IntPoint quantizeScale = QuantizeScale(aFont, currentTransform);
4323 Matrix quantizeTransform = currentTransform;
4324 quantizeTransform.PostScale(quantizeScale.x, quantizeScale.y);
4325 HashNumber hash =
4326 GlyphCacheEntry::HashGlyphs(aBuffer, quantizeTransform, quantizeScale);
4327 DeviceColor colorOrMask =
4328 useBitmaps
4329 ? color
4330 : DeviceColor::Mask(aUseSubpixelAA ? 1 : 0, lightOnDark ? 1 : 0);
4331 IntRect clipRect(IntPoint(), mViewportSize);
4332 RefPtr<GlyphCacheEntry> entry =
4333 cache->FindEntry(aBuffer, colorOrMask, quantizeTransform, quantizeScale,
4334 clipRect, hash, aStrokeOptions);
4335 if (!entry) {
4336 // For small text runs, bounds computations can be expensive relative to the
4337 // cost of looking up a cache result. Avoid doing local bounds computations
4338 // until actually inserting the entry into the cache.
4339 Maybe<Rect> bounds = mCurrentTarget->mSkia->GetGlyphLocalBounds(
4340 aFont, aBuffer, aPattern, aStrokeOptions, aOptions);
4341 if (!bounds) {
4342 return true;
4344 // Transform the local bounds into device space so that we know how big
4345 // the cached texture will be.
4346 Rect xformBounds = currentTransform.TransformBounds(*bounds);
4347 // Check if the transform flattens out the bounds before rounding.
4348 if (xformBounds.IsEmpty()) {
4349 return true;
4351 IntRect fullBounds = RoundedOut(currentTransform.TransformBounds(*bounds));
4352 IntRect clipBounds = fullBounds.Intersect(clipRect);
4353 // Check if the bounds are completely clipped out.
4354 if (clipBounds.IsEmpty()) {
4355 return true;
4357 entry = cache->InsertEntry(aBuffer, colorOrMask, quantizeTransform,
4358 quantizeScale, clipBounds, fullBounds, hash,
4359 aStrokeOptions);
4360 if (!entry) {
4361 return false;
4365 // The bounds of the entry may have a different transform offset from the
4366 // bounds of the currently drawn text run. The entry bounds are relative to
4367 // the entry's quantized offset already, so just move the bounds to the new
4368 // offset.
4369 IntRect intBounds = entry->GetBounds();
4370 IntPoint newOffset =
4371 QuantizeOffset(quantizeTransform, quantizeScale, aBuffer);
4372 intBounds +=
4373 IntPoint(newOffset.x / quantizeScale.x, newOffset.y / quantizeScale.y);
4374 // Ensure there is a clear border around the text. This must be applied only
4375 // after clipping so that we always have some border texels for filtering.
4376 intBounds.Inflate(2);
4378 RefPtr<TextureHandle> handle = entry->GetHandle();
4379 if (handle && handle->IsValid()) {
4380 // If there is an entry with a valid cached texture handle, then try
4381 // to draw with that. If that for some reason failed, then fall back
4382 // to using the Skia target as that means we were preventing from
4383 // drawing to the WebGL context based on something other than the
4384 // texture.
4385 SurfacePattern pattern(nullptr, ExtendMode::CLAMP,
4386 Matrix::Translation(intBounds.TopLeft()));
4387 if (DrawRectAccel(Rect(intBounds), pattern, aOptions,
4388 useBitmaps ? Nothing() : Some(color), &handle, false,
4389 true, true)) {
4390 return true;
4392 } else {
4393 handle = nullptr;
4395 // If we get here, either there wasn't a cached texture handle or it
4396 // wasn't valid. Render the text run into a temporary target.
4397 RefPtr<DrawTargetSkia> textDT = new DrawTargetSkia;
4398 if (textDT->Init(intBounds.Size(),
4399 lightOnDark && !useBitmaps && !aUseSubpixelAA
4400 ? SurfaceFormat::A8
4401 : SurfaceFormat::B8G8R8A8)) {
4402 if (!lightOnDark) {
4403 // If rendering dark-on-light text, we need to clear the background to
4404 // white while using an opaque alpha value to allow this.
4405 textDT->FillRect(Rect(IntRect(IntPoint(), intBounds.Size())),
4406 ColorPattern(DeviceColor(1, 1, 1, 1)),
4407 DrawOptions(1.0f, CompositionOp::OP_OVER));
4409 textDT->SetTransform(currentTransform *
4410 Matrix::Translation(-intBounds.TopLeft()));
4411 textDT->SetPermitSubpixelAA(aUseSubpixelAA);
4412 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER,
4413 aOptions.mAntialiasMode);
4414 // If bitmaps might be used, then we have to supply the color, as color
4415 // emoji may ignore it while grayscale bitmaps may use it, with no way to
4416 // know ahead of time. Otherwise, assume the output will be a mask and
4417 // just render it white to determine intensity. Depending on whether the
4418 // text is light or dark, we render white or black text respectively.
4419 ColorPattern colorPattern(
4420 useBitmaps ? color : DeviceColor::Mask(lightOnDark ? 1 : 0, 1));
4421 if (aStrokeOptions) {
4422 textDT->StrokeGlyphs(aFont, aBuffer, colorPattern, *aStrokeOptions,
4423 drawOptions);
4424 } else {
4425 textDT->FillGlyphs(aFont, aBuffer, colorPattern, drawOptions);
4427 if (!lightOnDark) {
4428 uint8_t* data = nullptr;
4429 IntSize size;
4430 int32_t stride = 0;
4431 SurfaceFormat format = SurfaceFormat::UNKNOWN;
4432 if (!textDT->LockBits(&data, &size, &stride, &format)) {
4433 return false;
4435 uint8_t* row = data;
4436 for (int y = 0; y < size.height; ++y) {
4437 uint8_t* px = row;
4438 for (int x = 0; x < size.width; ++x) {
4439 // If rendering dark-on-light text, we need to invert the final mask
4440 // so that it is in the expected white text on transparent black
4441 // format. The alpha will be initialized to the largest of the
4442 // values.
4443 px[0] = 255 - px[0];
4444 px[1] = 255 - px[1];
4445 px[2] = 255 - px[2];
4446 px[3] = std::max(px[0], std::max(px[1], px[2]));
4447 px += 4;
4449 row += stride;
4451 textDT->ReleaseBits(data);
4453 RefPtr<SourceSurface> textSurface = textDT->Snapshot();
4454 if (textSurface) {
4455 // If we don't expect the text surface to contain color glyphs
4456 // such as from subpixel AA, then do one final check to see if
4457 // any ended up in the result. If not, extract the alpha values
4458 // from the surface so we can render it as a mask.
4459 if (textSurface->GetFormat() != SurfaceFormat::A8 &&
4460 !CheckForColorGlyphs(textSurface)) {
4461 textSurface = ExtractAlpha(textSurface, !useBitmaps);
4462 if (!textSurface) {
4463 // Failed extracting alpha for the text surface...
4464 return false;
4467 // Attempt to upload the rendered text surface into a texture
4468 // handle and draw it.
4469 SurfacePattern pattern(textSurface, ExtendMode::CLAMP,
4470 Matrix::Translation(intBounds.TopLeft()));
4471 if (DrawRectAccel(Rect(intBounds), pattern, aOptions,
4472 useBitmaps ? Nothing() : Some(color), &handle, false,
4473 true) &&
4474 handle) {
4475 // If drawing succeeded, then the text surface was uploaded to
4476 // a texture handle. Assign it to the glyph cache entry.
4477 entry->Link(handle);
4478 } else {
4479 // If drawing failed, remove the entry from the cache.
4480 entry->Unlink();
4482 return true;
4486 return false;
4489 void DrawTargetWebgl::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
4490 const Pattern& aPattern,
4491 const DrawOptions& aOptions) {
4492 if (!aFont || !aBuffer.mNumGlyphs) {
4493 return;
4496 bool useSubpixelAA = ShouldUseSubpixelAA(aFont, aOptions);
4498 if (mWebglValid && SupportsDrawOptions(aOptions) &&
4499 aPattern.GetType() == PatternType::COLOR && PrepareContext() &&
4500 mSharedContext->DrawGlyphsAccel(aFont, aBuffer, aPattern, aOptions,
4501 nullptr, useSubpixelAA)) {
4502 return;
4505 // If not able to cache the text run to a texture, then just fall back to
4506 // drawing with the Skia target.
4507 if (useSubpixelAA) {
4508 // Subpixel AA does not support layering because the subpixel masks can't
4509 // blend with the over op.
4510 MarkSkiaChanged();
4511 } else {
4512 MarkSkiaChanged(aOptions);
4514 mSkia->FillGlyphs(aFont, aBuffer, aPattern, aOptions);
4517 // Attempts to read the contents of the WebGL context into the Skia target.
4518 bool DrawTargetWebgl::ReadIntoSkia() {
4519 if (mSkiaValid) {
4520 return false;
4522 bool didReadback = false;
4523 if (mWebglValid) {
4524 uint8_t* data = nullptr;
4525 IntSize size;
4526 int32_t stride;
4527 SurfaceFormat format;
4528 if (mIsClear) {
4529 // If the WebGL target is still clear, then just clear the Skia target.
4530 mSkia->DetachAllSnapshots();
4531 mSkiaNoClip->FillRect(Rect(mSkiaNoClip->GetRect()), GetClearPattern(),
4532 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
4533 } else {
4534 // If there's no existing snapshot and we can successfully map the Skia
4535 // target for reading, then try to read into that.
4536 if (!mSnapshot && mSkia->LockBits(&data, &size, &stride, &format)) {
4537 (void)ReadInto(data, stride);
4538 mSkia->ReleaseBits(data);
4539 } else if (RefPtr<SourceSurface> snapshot = Snapshot()) {
4540 // Otherwise, fall back to getting a snapshot from WebGL if available
4541 // and then copying that to Skia.
4542 mSkia->CopySurface(snapshot, GetRect(), IntPoint(0, 0));
4544 didReadback = true;
4547 mSkiaValid = true;
4548 // The Skia data is flat after reading, so disable any layering.
4549 mSkiaLayer = false;
4550 return didReadback;
4553 // Reads data from the WebGL context and blends it with the current Skia layer.
4554 void DrawTargetWebgl::FlattenSkia() {
4555 if (!mSkiaValid || !mSkiaLayer) {
4556 return;
4558 mSkiaLayer = false;
4559 if (mSkiaLayerClear) {
4560 // If the WebGL target is clear, then there is nothing to blend.
4561 return;
4563 if (RefPtr<DataSourceSurface> base = ReadSnapshot()) {
4564 mSkia->DetachAllSnapshots();
4565 mSkiaNoClip->DrawSurface(base, Rect(GetRect()), Rect(GetRect()),
4566 DrawSurfaceOptions(SamplingFilter::POINT),
4567 DrawOptions(1.f, CompositionOp::OP_DEST_OVER));
4571 // Attempts to draw the contents of the Skia target into the WebGL context.
4572 bool DrawTargetWebgl::FlushFromSkia() {
4573 // If the WebGL context has been lost, then mark it as invalid and fail.
4574 if (mSharedContext->IsContextLost()) {
4575 mWebglValid = false;
4576 return false;
4578 // The WebGL target is already valid, so there is nothing to do.
4579 if (mWebglValid) {
4580 return true;
4582 // Ensure that DrawRect doesn't recursively call into FlushFromSkia. If
4583 // the Skia target isn't valid, then it doesn't matter what is in the the
4584 // WebGL target either, so only try to blend if there is a valid Skia target.
4585 mWebglValid = true;
4586 if (mSkiaValid) {
4587 AutoRestoreContext restore(this);
4589 // If the Skia target is clear, then there is no need to use a snapshot.
4590 // Directly clear the WebGL target instead.
4591 if (mIsClear) {
4592 if (!DrawRect(Rect(GetRect()), GetClearPattern(),
4593 DrawOptions(1.0f, CompositionOp::OP_SOURCE), Nothing(),
4594 nullptr, false, false, true)) {
4595 mWebglValid = false;
4596 return false;
4598 return true;
4601 RefPtr<SourceSurface> skiaSnapshot = mSkia->Snapshot();
4602 if (!skiaSnapshot) {
4603 // There's a valid Skia target to draw to, but for some reason there is
4604 // no available snapshot, so just keep using the Skia target.
4605 mWebglValid = false;
4606 return false;
4609 // If there is no layer, then just upload it directly.
4610 if (!mSkiaLayer) {
4611 if (PrepareContext(false) && MarkChanged()) {
4612 if (RefPtr<DataSourceSurface> data = skiaSnapshot->GetDataSurface()) {
4613 mSharedContext->UploadSurface(data, mFormat, GetRect(), IntPoint(),
4614 false, false, mTex);
4615 return true;
4618 // Failed to upload the Skia snapshot.
4619 mWebglValid = false;
4620 return false;
4623 SurfacePattern pattern(skiaSnapshot, ExtendMode::CLAMP);
4624 // If there is a layer, blend the snapshot with the WebGL context.
4625 if (!DrawRect(Rect(GetRect()), pattern,
4626 DrawOptions(1.0f, CompositionOp::OP_OVER), Nothing(),
4627 &mSnapshotTexture, false, false, true, true)) {
4628 // If accelerated drawing failed for some reason, then leave the Skia
4629 // target unchanged.
4630 mWebglValid = false;
4631 return false;
4634 return true;
4637 void DrawTargetWebgl::UsageProfile::BeginFrame() {
4638 // Reset the usage profile counters for the new frame.
4639 mFallbacks = 0;
4640 mLayers = 0;
4641 mCacheMisses = 0;
4642 mCacheHits = 0;
4643 mUncachedDraws = 0;
4644 mReadbacks = 0;
4647 void DrawTargetWebgl::UsageProfile::EndFrame() {
4648 bool failed = false;
4649 // If we hit a complete fallback to software rendering, or if cache misses
4650 // were more than cutoff ratio of all requests, then we consider the frame as
4651 // having failed performance profiling.
4652 float cacheRatio =
4653 StaticPrefs::gfx_canvas_accelerated_profile_cache_miss_ratio();
4654 if (mFallbacks > 0 ||
4655 float(mCacheMisses + mReadbacks + mLayers) >
4656 cacheRatio * float(mCacheMisses + mCacheHits + mUncachedDraws +
4657 mReadbacks + mLayers)) {
4658 failed = true;
4660 if (failed) {
4661 ++mFailedFrames;
4663 ++mFrameCount;
4666 bool DrawTargetWebgl::UsageProfile::RequiresRefresh() const {
4667 // If we've rendered at least the required number of frames for a profile and
4668 // more than the cutoff ratio of frames did not meet performance criteria,
4669 // then we should stop using an accelerated canvas.
4670 uint32_t profileFrames = StaticPrefs::gfx_canvas_accelerated_profile_frames();
4671 if (!profileFrames || mFrameCount < profileFrames) {
4672 return false;
4674 float failRatio =
4675 StaticPrefs::gfx_canvas_accelerated_profile_fallback_ratio();
4676 return mFailedFrames > failRatio * mFrameCount;
4679 void SharedContextWebgl::CachePrefs() {
4680 uint32_t capacity = StaticPrefs::gfx_canvas_accelerated_gpu_path_size() << 20;
4681 if (capacity != mPathVertexCapacity) {
4682 mPathVertexCapacity = capacity;
4683 if (mPathCache) {
4684 mPathCache->ClearVertexRanges();
4686 if (mPathVertexBuffer) {
4687 ResetPathVertexBuffer();
4691 mPathMaxComplexity =
4692 StaticPrefs::gfx_canvas_accelerated_gpu_path_complexity();
4694 mPathAAStroke = StaticPrefs::gfx_canvas_accelerated_aa_stroke_enabled();
4695 mPathWGRStroke = StaticPrefs::gfx_canvas_accelerated_stroke_to_fill_path();
4698 // For use within CanvasRenderingContext2D, called on BorrowDrawTarget.
4699 void DrawTargetWebgl::BeginFrame(bool aInvalidContents) {
4700 // If still rendering into the Skia target, switch back to the WebGL
4701 // context.
4702 if (!mWebglValid) {
4703 if (aInvalidContents) {
4704 // If nothing needs to persist, just mark the WebGL context valid.
4705 mWebglValid = true;
4706 // Even if the Skia framebuffer is marked clear, since the WebGL
4707 // context is not valid, its contents may be out-of-date and not
4708 // necessarily clear.
4709 mIsClear = false;
4710 } else {
4711 FlushFromSkia();
4714 // Check if we need to clear out any cached because of memory pressure.
4715 mSharedContext->ClearCachesIfNecessary();
4716 // Cache any prefs for the frame.
4717 mSharedContext->CachePrefs();
4718 mProfile.BeginFrame();
4721 // For use within CanvasRenderingContext2D, called on ReturnDrawTarget.
4722 void DrawTargetWebgl::EndFrame() {
4723 if (StaticPrefs::gfx_canvas_accelerated_debug()) {
4724 // Draw a green rectangle in the upper right corner to indicate
4725 // acceleration.
4726 IntRect corner = IntRect(mSize.width - 16, 0, 16, 16).Intersect(GetRect());
4727 DrawRect(Rect(corner), ColorPattern(DeviceColor(0.0f, 1.0f, 0.0f, 1.0f)),
4728 DrawOptions(), Nothing(), nullptr, false, false);
4730 mProfile.EndFrame();
4731 // Ensure we're not somehow using more than the allowed texture memory.
4732 mSharedContext->PruneTextureMemory();
4733 // Signal that we're done rendering the frame in case no present occurs.
4734 mSharedContext->mWebgl->EndOfFrame();
4735 // Check if we need to clear out any cached because of memory pressure.
4736 mSharedContext->ClearCachesIfNecessary();
4739 bool DrawTargetWebgl::CopyToSwapChain(
4740 layers::TextureType aTextureType, layers::RemoteTextureId aId,
4741 layers::RemoteTextureOwnerId aOwnerId,
4742 layers::RemoteTextureOwnerClient* aOwnerClient) {
4743 if (!mWebglValid && !FlushFromSkia()) {
4744 return false;
4747 // Copy and swizzle the WebGL framebuffer to the swap chain front buffer.
4748 webgl::SwapChainOptions options;
4749 options.bgra = true;
4750 // Allow async present to be toggled on for accelerated Canvas2D
4751 // independent of WebGL via pref.
4752 options.forceAsyncPresent =
4753 StaticPrefs::gfx_canvas_accelerated_async_present();
4754 options.remoteTextureId = aId;
4755 options.remoteTextureOwnerId = aOwnerId;
4756 return mSharedContext->mWebgl->CopyToSwapChain(mFramebuffer, aTextureType,
4757 options, aOwnerClient);
4760 already_AddRefed<DrawTarget> DrawTargetWebgl::CreateSimilarDrawTarget(
4761 const IntSize& aSize, SurfaceFormat aFormat) const {
4762 return mSkia->CreateSimilarDrawTarget(aSize, aFormat);
4765 bool DrawTargetWebgl::CanCreateSimilarDrawTarget(const IntSize& aSize,
4766 SurfaceFormat aFormat) const {
4767 return mSkia->CanCreateSimilarDrawTarget(aSize, aFormat);
4770 RefPtr<DrawTarget> DrawTargetWebgl::CreateClippedDrawTarget(
4771 const Rect& aBounds, SurfaceFormat aFormat) {
4772 return mSkia->CreateClippedDrawTarget(aBounds, aFormat);
4775 already_AddRefed<SourceSurface> DrawTargetWebgl::CreateSourceSurfaceFromData(
4776 unsigned char* aData, const IntSize& aSize, int32_t aStride,
4777 SurfaceFormat aFormat) const {
4778 return mSkia->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat);
4781 already_AddRefed<SourceSurface>
4782 DrawTargetWebgl::CreateSourceSurfaceFromNativeSurface(
4783 const NativeSurface& aSurface) const {
4784 return mSkia->CreateSourceSurfaceFromNativeSurface(aSurface);
4787 already_AddRefed<SourceSurface> DrawTargetWebgl::OptimizeSourceSurface(
4788 SourceSurface* aSurface) const {
4789 if (aSurface->GetType() == SurfaceType::WEBGL) {
4790 return do_AddRef(aSurface);
4792 return mSkia->OptimizeSourceSurface(aSurface);
4795 already_AddRefed<SourceSurface>
4796 DrawTargetWebgl::OptimizeSourceSurfaceForUnknownAlpha(
4797 SourceSurface* aSurface) const {
4798 return mSkia->OptimizeSourceSurfaceForUnknownAlpha(aSurface);
4801 already_AddRefed<GradientStops> DrawTargetWebgl::CreateGradientStops(
4802 GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const {
4803 return mSkia->CreateGradientStops(aStops, aNumStops, aExtendMode);
4806 already_AddRefed<FilterNode> DrawTargetWebgl::CreateFilter(FilterType aType) {
4807 return mSkia->CreateFilter(aType);
4810 void DrawTargetWebgl::DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
4811 const Point& aDestPoint,
4812 const DrawOptions& aOptions) {
4813 MarkSkiaChanged(aOptions);
4814 mSkia->DrawFilter(aNode, aSourceRect, aDestPoint, aOptions);
4817 bool DrawTargetWebgl::Draw3DTransformedSurface(SourceSurface* aSurface,
4818 const Matrix4x4& aMatrix) {
4819 MarkSkiaChanged();
4820 return mSkia->Draw3DTransformedSurface(aSurface, aMatrix);
4823 void DrawTargetWebgl::PushLayer(bool aOpaque, Float aOpacity,
4824 SourceSurface* aMask,
4825 const Matrix& aMaskTransform,
4826 const IntRect& aBounds, bool aCopyBackground) {
4827 PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds,
4828 aCopyBackground, CompositionOp::OP_OVER);
4831 void DrawTargetWebgl::PushLayerWithBlend(bool aOpaque, Float aOpacity,
4832 SourceSurface* aMask,
4833 const Matrix& aMaskTransform,
4834 const IntRect& aBounds,
4835 bool aCopyBackground,
4836 CompositionOp aCompositionOp) {
4837 MarkSkiaChanged(DrawOptions(aOpacity, aCompositionOp));
4838 mSkia->PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds,
4839 aCopyBackground, aCompositionOp);
4840 ++mLayerDepth;
4841 SetPermitSubpixelAA(mSkia->GetPermitSubpixelAA());
4844 void DrawTargetWebgl::PopLayer() {
4845 MOZ_ASSERT(mSkiaValid);
4846 MOZ_ASSERT(mLayerDepth > 0);
4847 --mLayerDepth;
4848 mSkia->PopLayer();
4849 SetPermitSubpixelAA(mSkia->GetPermitSubpixelAA());
4852 } // namespace mozilla::gfx