Bug 1795723 - Unified extensions UI should support High Contrast Mode. r=ayeddi,deskt...
[gecko.git] / dom / canvas / DrawTargetWebgl.cpp
blobfb3f605883bdd1689b1484f810c535ebf0e378ff
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/Blur.h"
13 #include "mozilla/gfx/DrawTargetSkia.h"
14 #include "mozilla/gfx/Helpers.h"
15 #include "mozilla/gfx/HelpersSkia.h"
16 #include "mozilla/gfx/Logging.h"
17 #include "mozilla/gfx/PathSkia.h"
18 #include "mozilla/gfx/Swizzle.h"
19 #include "mozilla/layers/ImageDataSerializer.h"
21 #include "ClientWebGLContext.h"
22 #include "WebGLChild.h"
24 #include "gfxPlatform.h"
26 namespace mozilla::gfx {
28 // Inserts (allocates) a rectangle of the requested size into the tree.
29 Maybe<IntPoint> TexturePacker::Insert(const IntSize& aSize) {
30 // Check if the available space could possibly fit the requested size. If
31 // not, there is no reason to continue searching within this sub-tree.
32 if (mAvailable < std::min(aSize.width, aSize.height) ||
33 mBounds.width < aSize.width || mBounds.height < aSize.height) {
34 return Nothing();
36 if (mChildren) {
37 // If this node has children, then try to insert into each of the children
38 // in turn.
39 Maybe<IntPoint> inserted = mChildren[0].Insert(aSize);
40 if (!inserted) {
41 inserted = mChildren[1].Insert(aSize);
43 // If the insertion succeeded, adjust the available state to reflect the
44 // remaining space in the children.
45 if (inserted) {
46 mAvailable = std::max(mChildren[0].mAvailable, mChildren[1].mAvailable);
47 if (!mAvailable) {
48 DiscardChildren();
51 return inserted;
53 // If we get here, we've encountered a leaf node. First check if its size is
54 // exactly the requested size. If so, mark the node as unavailable and return
55 // its offset.
56 if (mBounds.Size() == aSize) {
57 mAvailable = 0;
58 return Some(mBounds.TopLeft());
60 // The node is larger than the requested size. Choose the axis which has the
61 // most excess space beyond the requested size and split it so that at least
62 // one of the children matches the requested size for that axis.
63 if (mBounds.width - aSize.width > mBounds.height - aSize.height) {
64 mChildren.reset(new TexturePacker[2]{
65 TexturePacker(
66 IntRect(mBounds.x, mBounds.y, aSize.width, mBounds.height)),
67 TexturePacker(IntRect(mBounds.x + aSize.width, mBounds.y,
68 mBounds.width - aSize.width, mBounds.height))});
69 } else {
70 mChildren.reset(new TexturePacker[2]{
71 TexturePacker(
72 IntRect(mBounds.x, mBounds.y, mBounds.width, aSize.height)),
73 TexturePacker(IntRect(mBounds.x, mBounds.y + aSize.height,
74 mBounds.width, mBounds.height - aSize.height))});
76 // After splitting, try to insert into the first child, which should usually
77 // be big enough to accomodate the request. Adjust the available state to the
78 // remaining space.
79 Maybe<IntPoint> inserted = mChildren[0].Insert(aSize);
80 mAvailable = std::max(mChildren[0].mAvailable, mChildren[1].mAvailable);
81 return inserted;
84 // Removes (frees) a rectangle with the given bounds from the tree.
85 bool TexturePacker::Remove(const IntRect& aBounds) {
86 if (!mChildren) {
87 // If there are no children, we encountered a leaf node. Non-zero available
88 // state means that this node was already removed previously. Also, if the
89 // bounds don't contain the request, and assuming the tree was previously
90 // split during insertion, then this node is not the node we're searching
91 // for.
92 if (mAvailable > 0 || !mBounds.Contains(aBounds)) {
93 return false;
95 // The bounds match exactly and it was previously inserted, so in this case
96 // we can just remove it.
97 if (mBounds == aBounds) {
98 mAvailable = std::min(mBounds.width, mBounds.height);
99 return true;
101 // We need to split this leaf node so that it can exactly match the removed
102 // bounds. We know the leaf node at least contains the removed bounds, but
103 // needs to be subdivided until it has a child node that exactly matches.
104 // Choose the axis to split with the largest amount of excess space. Within
105 // that axis, choose the larger of the space before or after the subrect as
106 // the split point to the new children.
107 if (mBounds.width - aBounds.width > mBounds.height - aBounds.height) {
108 int split = aBounds.x - mBounds.x > mBounds.XMost() - aBounds.XMost()
109 ? aBounds.x
110 : aBounds.XMost();
111 mChildren.reset(new TexturePacker[2]{
112 TexturePacker(
113 IntRect(mBounds.x, mBounds.y, split - mBounds.x, mBounds.height),
114 false),
115 TexturePacker(IntRect(split, mBounds.y, mBounds.XMost() - split,
116 mBounds.height),
117 false)});
118 } else {
119 int split = aBounds.y - mBounds.y > mBounds.YMost() - aBounds.YMost()
120 ? aBounds.y
121 : aBounds.YMost();
122 mChildren.reset(new TexturePacker[2]{
123 TexturePacker(
124 IntRect(mBounds.x, mBounds.y, mBounds.width, split - mBounds.y),
125 false),
126 TexturePacker(
127 IntRect(mBounds.x, split, mBounds.width, mBounds.YMost() - split),
128 false)});
131 // We've encountered a branch node. Determine which of the two child nodes
132 // would possibly contain the removed bounds. We first check which axis the
133 // children were split on and then whether the removed bounds on that axis
134 // are past the start of the second child. Proceed to recurse into that
135 // child node for removal.
136 bool next = mChildren[0].mBounds.x < mChildren[1].mBounds.x
137 ? aBounds.x >= mChildren[1].mBounds.x
138 : aBounds.y >= mChildren[1].mBounds.y;
139 bool removed = mChildren[next ? 1 : 0].Remove(aBounds);
140 if (removed) {
141 if (mChildren[0].IsFullyAvailable() && mChildren[1].IsFullyAvailable()) {
142 DiscardChildren();
143 mAvailable = std::min(mBounds.width, mBounds.height);
144 } else {
145 mAvailable = std::max(mChildren[0].mAvailable, mChildren[1].mAvailable);
148 return removed;
151 SharedTexture::SharedTexture(const IntSize& aSize, SurfaceFormat aFormat,
152 const RefPtr<WebGLTextureJS>& aTexture)
153 : mPacker(IntRect(IntPoint(0, 0), aSize)),
154 mFormat(aFormat),
155 mTexture(aTexture) {}
157 SharedTextureHandle::SharedTextureHandle(const IntRect& aBounds,
158 SharedTexture* aTexture)
159 : mBounds(aBounds), mTexture(aTexture) {}
161 already_AddRefed<SharedTextureHandle> SharedTexture::Allocate(
162 const IntSize& aSize) {
163 RefPtr<SharedTextureHandle> handle;
164 if (Maybe<IntPoint> origin = mPacker.Insert(aSize)) {
165 handle = new SharedTextureHandle(IntRect(*origin, aSize), this);
166 ++mAllocatedHandles;
168 return handle.forget();
171 bool SharedTexture::Free(const SharedTextureHandle& aHandle) {
172 if (aHandle.mTexture != this) {
173 return false;
175 if (!mPacker.Remove(aHandle.mBounds)) {
176 return false;
178 --mAllocatedHandles;
179 return true;
182 StandaloneTexture::StandaloneTexture(const IntSize& aSize,
183 SurfaceFormat aFormat,
184 const RefPtr<WebGLTextureJS>& aTexture)
185 : mSize(aSize), mFormat(aFormat), mTexture(aTexture) {}
187 DrawTargetWebgl::DrawTargetWebgl() = default;
189 inline void DrawTargetWebgl::SharedContext::ClearLastTexture() {
190 mLastTexture = nullptr;
191 mLastClipMask = nullptr;
194 // Attempts to clear the snapshot state. If the snapshot is only referenced by
195 // this target, then it should simply be destroyed. If it is a WebGL surface in
196 // use by something else, then special cleanup such as reusing the texture or
197 // copy-on-write may be possible.
198 void DrawTargetWebgl::ClearSnapshot(bool aCopyOnWrite, bool aNeedHandle) {
199 if (!mSnapshot) {
200 return;
202 mSharedContext->ClearLastTexture();
203 if (mSnapshot->hasOneRef() || mSnapshot->GetType() != SurfaceType::WEBGL) {
204 mSnapshot = nullptr;
205 return;
207 RefPtr<SourceSurfaceWebgl> snapshot =
208 mSnapshot.forget().downcast<SourceSurfaceWebgl>();
209 if (aCopyOnWrite) {
210 // WebGL snapshots must be notified that the framebuffer contents will be
211 // changing so that it can copy the data.
212 snapshot->DrawTargetWillChange(aNeedHandle);
213 } else {
214 // If not copying, then give the backing texture to the surface for reuse.
215 snapshot->GiveTexture(
216 mSharedContext->WrapSnapshot(GetSize(), GetFormat(), mTex.forget()));
220 DrawTargetWebgl::~DrawTargetWebgl() {
221 ClearSnapshot(false);
222 if (mSharedContext) {
223 if (mShmem.IsWritable()) {
224 // Force any Skia snapshots to copy the shmem before it deallocs.
225 mSkia->DetachAllSnapshots();
226 // Ensure we're done using the shmem before dealloc.
227 mSharedContext->WaitForShmem(this);
228 auto* child = mSharedContext->mWebgl->GetChild();
229 if (child && child->CanSend()) {
230 child->DeallocShmem(mShmem);
233 if (mClipMask) {
234 mSharedContext->mWebgl->DeleteTexture(mClipMask);
236 if (mFramebuffer) {
237 mSharedContext->mWebgl->DeleteFramebuffer(mFramebuffer);
239 if (mTex) {
240 mSharedContext->mWebgl->DeleteTexture(mTex);
245 DrawTargetWebgl::SharedContext::SharedContext() = default;
247 DrawTargetWebgl::SharedContext::~SharedContext() {
248 if (sSharedContext.init() && sSharedContext.get() == this) {
249 sSharedContext.set(nullptr);
251 // Detect context loss before deletion.
252 if (mWebgl) {
253 mWebgl->ActiveTexture(LOCAL_GL_TEXTURE0);
255 ClearAllTextures();
256 UnlinkSurfaceTextures();
257 UnlinkGlyphCaches();
260 // Remove any SourceSurface user data associated with this TextureHandle.
261 inline void DrawTargetWebgl::SharedContext::UnlinkSurfaceTexture(
262 const RefPtr<TextureHandle>& aHandle) {
263 if (SourceSurface* surface = aHandle->GetSurface()) {
264 // Ensure any WebGL snapshot textures get unlinked.
265 if (surface->GetType() == SurfaceType::WEBGL) {
266 static_cast<SourceSurfaceWebgl*>(surface)->OnUnlinkTexture(this);
268 surface->RemoveUserData(aHandle->IsShadow() ? &mShadowTextureKey
269 : &mTextureHandleKey);
273 // Unlinks TextureHandles from any SourceSurface user data.
274 void DrawTargetWebgl::SharedContext::UnlinkSurfaceTextures() {
275 for (RefPtr<TextureHandle> handle = mTextureHandles.getFirst(); handle;
276 handle = handle->getNext()) {
277 UnlinkSurfaceTexture(handle);
281 // Unlinks GlyphCaches from any ScaledFont user data.
282 void DrawTargetWebgl::SharedContext::UnlinkGlyphCaches() {
283 GlyphCache* cache = mGlyphCaches.getFirst();
284 while (cache) {
285 ScaledFont* font = cache->GetFont();
286 // Access the next cache before removing the user data, as it might destroy
287 // the cache.
288 cache = cache->getNext();
289 font->RemoveUserData(&mGlyphCacheKey);
293 void DrawTargetWebgl::SharedContext::OnMemoryPressure() {
294 mShouldClearCaches = true;
297 // Clear out the entire list of texture handles from any source.
298 void DrawTargetWebgl::SharedContext::ClearAllTextures() {
299 while (!mTextureHandles.isEmpty()) {
300 PruneTextureHandle(mTextureHandles.popLast());
301 --mNumTextureHandles;
305 // Scan through the shared texture pages looking for any that are empty and
306 // delete them.
307 void DrawTargetWebgl::SharedContext::ClearEmptyTextureMemory() {
308 for (auto pos = mSharedTextures.begin(); pos != mSharedTextures.end();) {
309 if (!(*pos)->HasAllocatedHandles()) {
310 RefPtr<SharedTexture> shared = *pos;
311 size_t usedBytes = shared->UsedBytes();
312 mEmptyTextureMemory -= usedBytes;
313 mTotalTextureMemory -= usedBytes;
314 pos = mSharedTextures.erase(pos);
315 mWebgl->DeleteTexture(shared->GetWebGLTexture());
316 } else {
317 ++pos;
322 // If there is a request to clear out the caches because of memory pressure,
323 // then first clear out all the texture handles in the texture cache. If there
324 // are still empty texture pages being kept around, then clear those too.
325 void DrawTargetWebgl::SharedContext::ClearCachesIfNecessary() {
326 if (!mShouldClearCaches.exchange(false)) {
327 return;
329 mZeroBuffer = nullptr;
330 ClearAllTextures();
331 if (mEmptyTextureMemory) {
332 ClearEmptyTextureMemory();
334 ClearLastTexture();
337 // If a non-recoverable error occurred that would stop the canvas from initing.
338 static Atomic<bool> sContextInitError(false);
340 MOZ_THREAD_LOCAL(DrawTargetWebgl::SharedContext*)
341 DrawTargetWebgl::sSharedContext;
343 RefPtr<DrawTargetWebgl::SharedContext> DrawTargetWebgl::sMainSharedContext;
345 // Try to initialize a new WebGL context. Verifies that the requested size does
346 // not exceed the available texture limits and that shader creation succeeded.
347 bool DrawTargetWebgl::Init(const IntSize& size, const SurfaceFormat format) {
348 MOZ_ASSERT(format == SurfaceFormat::B8G8R8A8 ||
349 format == SurfaceFormat::B8G8R8X8);
351 mSize = size;
352 mFormat = format;
354 if (!sSharedContext.init()) {
355 return false;
358 DrawTargetWebgl::SharedContext* sharedContext = sSharedContext.get();
359 if (!sharedContext || sharedContext->IsContextLost()) {
360 mSharedContext = new DrawTargetWebgl::SharedContext;
361 if (!mSharedContext->Initialize()) {
362 mSharedContext = nullptr;
363 return false;
366 sSharedContext.set(mSharedContext.get());
368 if (NS_IsMainThread()) {
369 // Keep the shared context alive for the main thread by adding a ref.
370 // Ensure the ref will get cleared on shutdown so it doesn't leak.
371 if (!sMainSharedContext) {
372 ClearOnShutdown(&sMainSharedContext);
374 sMainSharedContext = mSharedContext;
376 } else {
377 mSharedContext = sharedContext;
380 if (size_t(std::max(size.width, size.height)) >
381 mSharedContext->mMaxTextureSize) {
382 return false;
385 if (!CreateFramebuffer()) {
386 return false;
389 auto* child = mSharedContext->mWebgl->GetChild();
390 if (child && child->CanSend()) {
391 size_t byteSize = layers::ImageDataSerializer::ComputeRGBBufferSize(
392 mSize, SurfaceFormat::B8G8R8A8);
393 if (byteSize) {
394 (void)child->AllocUnsafeShmem(byteSize, &mShmem);
397 mSkia = new DrawTargetSkia;
398 if (mShmem.IsWritable()) {
399 auto stride = layers::ImageDataSerializer::ComputeRGBStride(
400 SurfaceFormat::B8G8R8A8, size.width);
401 if (!mSkia->Init(mShmem.get<uint8_t>(), size, stride,
402 SurfaceFormat::B8G8R8A8, true)) {
403 return false;
405 } else if (!mSkia->Init(size, SurfaceFormat::B8G8R8A8)) {
406 return false;
408 SetPermitSubpixelAA(IsOpaque(format));
409 return true;
412 bool DrawTargetWebgl::SharedContext::Initialize() {
413 WebGLContextOptions options = {};
414 options.alpha = true;
415 options.depth = false;
416 options.stencil = false;
417 options.antialias = false;
418 options.preserveDrawingBuffer = true;
419 options.failIfMajorPerformanceCaveat = true;
421 mWebgl = new ClientWebGLContext(true);
422 mWebgl->SetContextOptions(options);
423 if (mWebgl->SetDimensions(1, 1) != NS_OK || mWebgl->IsContextLost()) {
424 mWebgl = nullptr;
425 return false;
428 mMaxTextureSize = mWebgl->Limits().maxTex2dSize;
430 CachePrefs();
432 if (!CreateShaders()) {
433 // There was a non-recoverable error when trying to init shaders.
434 sContextInitError = true;
435 mWebgl = nullptr;
436 return false;
439 return true;
442 void DrawTargetWebgl::SharedContext::SetBlendState(
443 CompositionOp aOp, const Maybe<DeviceColor>& aColor) {
444 if (aOp == mLastCompositionOp && mLastBlendColor == aColor) {
445 return;
447 mLastCompositionOp = aOp;
448 mLastBlendColor = aColor;
449 // AA is not supported for all composition ops, so switching blend modes may
450 // cause a toggle in AA state. Certain ops such as OP_SOURCE require output
451 // alpha that is blended separately from AA coverage. This would require two
452 // stage blending which can incur a substantial performance penalty, so to
453 // work around this currently we just disable AA for those ops.
454 mDirtyAA = true;
456 // Map the composition op to a WebGL blend mode, if possible.
457 mWebgl->Enable(LOCAL_GL_BLEND);
458 switch (aOp) {
459 case CompositionOp::OP_OVER:
460 if (aColor) {
461 // If a color is supplied, then we blend subpixel text.
462 mWebgl->BlendColor(aColor->b, aColor->g, aColor->r, 1.0f);
463 mWebgl->BlendFunc(LOCAL_GL_CONSTANT_COLOR,
464 LOCAL_GL_ONE_MINUS_SRC_COLOR);
465 } else {
466 mWebgl->BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
468 break;
469 case CompositionOp::OP_ADD:
470 mWebgl->BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE);
471 break;
472 case CompositionOp::OP_ATOP:
473 mWebgl->BlendFunc(LOCAL_GL_DST_ALPHA, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
474 break;
475 case CompositionOp::OP_SOURCE:
476 if (aColor) {
477 // If a color is supplied, then we assume there is clipping or AA. This
478 // requires that we still use an over blend func with the clip/AA alpha,
479 // while filling the interior with the unaltered color. Normally this
480 // would require dual source blending, but we can emulate it with only
481 // a blend color.
482 mWebgl->BlendColor(aColor->b, aColor->g, aColor->r, aColor->a);
483 mWebgl->BlendFunc(LOCAL_GL_CONSTANT_COLOR,
484 LOCAL_GL_ONE_MINUS_SRC_COLOR);
485 } else {
486 mWebgl->Disable(LOCAL_GL_BLEND);
488 break;
489 default:
490 mWebgl->Disable(LOCAL_GL_BLEND);
491 break;
495 // Ensure the WebGL framebuffer is set to the current target.
496 bool DrawTargetWebgl::SharedContext::SetTarget(DrawTargetWebgl* aDT) {
497 if (!mWebgl || mWebgl->IsContextLost()) {
498 return false;
500 if (aDT != mCurrentTarget) {
501 mCurrentTarget = aDT;
502 if (aDT) {
503 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, aDT->mFramebuffer);
504 mViewportSize = aDT->GetSize();
505 mWebgl->Viewport(0, 0, mViewportSize.width, mViewportSize.height);
506 // Force the viewport to be reset.
507 mDirtyViewport = true;
510 return true;
513 bool DrawTargetWebgl::SharedContext::SetClipMask(
514 const RefPtr<WebGLTextureJS>& aTex) {
515 if (mLastClipMask != aTex) {
516 if (!mWebgl) {
517 return false;
519 mWebgl->ActiveTexture(LOCAL_GL_TEXTURE1);
520 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, aTex);
521 mWebgl->ActiveTexture(LOCAL_GL_TEXTURE0);
522 mLastClipMask = aTex;
524 return true;
527 bool DrawTargetWebgl::SharedContext::SetNoClipMask() {
528 if (mNoClipMask) {
529 return SetClipMask(mNoClipMask);
531 if (!mWebgl) {
532 return false;
534 mNoClipMask = mWebgl->CreateTexture();
535 if (!mNoClipMask) {
536 return false;
538 mWebgl->ActiveTexture(LOCAL_GL_TEXTURE1);
539 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mNoClipMask);
540 static const uint8_t solidMask[4] = {0xFF, 0xFF, 0xFF, 0xFF};
541 mWebgl->RawTexImage(
542 0, LOCAL_GL_RGBA8, {0, 0, 0}, {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE},
543 {LOCAL_GL_TEXTURE_2D,
544 {1, 1, 1},
545 gfxAlphaType::NonPremult,
546 Some(RawBuffer(Range<const uint8_t>(solidMask, sizeof(solidMask))))});
547 InitTexParameters(mNoClipMask, false);
548 mWebgl->ActiveTexture(LOCAL_GL_TEXTURE0);
549 mLastClipMask = mNoClipMask;
550 return true;
553 // If the clip region can't be approximated by a simple clip rect, then we need
554 // to generate a clip mask that can represent the clip region per-pixel. We
555 // render to the Skia target temporarily, transparent outside the clip region,
556 // opaque inside, and upload this to a texture that can be used by the shaders.
557 bool DrawTargetWebgl::GenerateComplexClipMask() {
558 if (!mClipChanged) {
559 // If the clip mask was already generated, use the cached mask and bounds.
560 mSharedContext->SetClipMask(mClipMask);
561 mSharedContext->SetClipRect(mClipBounds);
562 return true;
564 if (!mWebglValid) {
565 // If the Skia target is currently being used, then we can't render the mask
566 // in it.
567 return false;
569 RefPtr<ClientWebGLContext> webgl = mSharedContext->mWebgl;
570 if (!webgl) {
571 return false;
573 bool init = false;
574 if (!mClipMask) {
575 mClipMask = webgl->CreateTexture();
576 if (!mClipMask) {
577 return false;
579 init = true;
581 // Try to get the bounds of the clip to limit the size of the mask.
582 if (Maybe<IntRect> clip = mSkia->GetDeviceClipRect(true)) {
583 mClipBounds = *clip;
584 } else {
585 // If we can't get bounds, then just use the entire viewport.
586 mClipBounds = IntRect(IntPoint(), mSize);
588 // If initializing the clip mask, then allocate the entire texture to ensure
589 // all pixels get filled with an empty mask regardless. Otherwise, restrict
590 // uploading to only the clip region.
591 RefPtr<DrawTargetSkia> dt = new DrawTargetSkia;
592 if (!dt->Init(mClipBounds.Size(), SurfaceFormat::A8)) {
593 return false;
595 // Set the clip region and fill the entire inside of it
596 // with opaque white.
597 for (auto& clipStack : mClipStack) {
598 dt->SetTransform(
599 Matrix(clipStack.mTransform).PostTranslate(-mClipBounds.TopLeft()));
600 if (clipStack.mPath) {
601 dt->PushClip(clipStack.mPath);
602 } else {
603 dt->PushClipRect(clipStack.mRect);
606 dt->SetTransform(Matrix::Translation(-mClipBounds.TopLeft()));
607 dt->FillRect(Rect(mClipBounds), ColorPattern(DeviceColor(1, 1, 1, 1)));
608 // Bind the clip mask for uploading.
609 webgl->ActiveTexture(LOCAL_GL_TEXTURE1);
610 webgl->BindTexture(LOCAL_GL_TEXTURE_2D, mClipMask);
611 if (init) {
612 mSharedContext->InitTexParameters(mClipMask, false);
614 RefPtr<DataSourceSurface> data;
615 if (RefPtr<SourceSurface> snapshot = dt->Snapshot()) {
616 data = snapshot->GetDataSurface();
618 // Finally, upload the texture data and initialize texture storage if
619 // necessary.
620 if (init && mClipBounds.Size() != mSize) {
621 mSharedContext->UploadSurface(nullptr, SurfaceFormat::A8,
622 IntRect(IntPoint(), mSize), IntPoint(), true,
623 true);
624 init = false;
626 mSharedContext->UploadSurface(data, SurfaceFormat::A8,
627 IntRect(IntPoint(), mClipBounds.Size()),
628 mClipBounds.TopLeft(), init);
629 webgl->ActiveTexture(LOCAL_GL_TEXTURE0);
630 // We already bound the texture, so notify the shared context that the clip
631 // mask changed to it.
632 mSharedContext->mLastClipMask = mClipMask;
633 mSharedContext->SetClipRect(mClipBounds);
634 // We uploaded a surface, just as if we missed the texture cache, so account
635 // for that here.
636 mProfile.OnCacheMiss();
637 return !!data;
640 bool DrawTargetWebgl::SetSimpleClipRect() {
641 // Determine whether the clipping rectangle is simple enough to accelerate.
642 // Check if there is a device space clip rectangle available from the Skia
643 // target.
644 Maybe<IntRect> clip = mSkia->GetDeviceClipRect(false);
645 if (!clip) {
646 return false;
648 // If the clip is empty, leave the final integer clip rectangle empty to
649 // trivially discard the draw request.
650 // If the clip rect is larger than the viewport, just set it to the
651 // viewport.
652 if (!clip->IsEmpty() && clip->Contains(IntRect(IntPoint(), mSize))) {
653 clip = Some(IntRect(IntPoint(), mSize));
655 mSharedContext->SetClipRect(*clip);
656 mSharedContext->SetNoClipMask();
657 return true;
660 // Installs the Skia clip rectangle, if applicable, onto the shared WebGL
661 // context as well as sets the WebGL framebuffer to the current target.
662 bool DrawTargetWebgl::PrepareContext(bool aClipped) {
663 if (!aClipped) {
664 // If no clipping requested, just set the clip rect to the viewport.
665 mSharedContext->SetClipRect(IntRect(IntPoint(), mSize));
666 mSharedContext->SetNoClipMask();
667 // Ensure the clip gets reset if clipping is later requested for the target.
668 mRefreshClipState = true;
669 } else if (mRefreshClipState || !mSharedContext->IsCurrentTarget(this)) {
670 // Try to use a simple clip rect if possible. Otherwise, fall back to
671 // generating a clip mask texture that can represent complex clip regions.
672 if (!SetSimpleClipRect() && !GenerateComplexClipMask()) {
673 return false;
675 mClipChanged = false;
676 mRefreshClipState = false;
678 return mSharedContext->SetTarget(this);
681 bool DrawTargetWebgl::SharedContext::IsContextLost() const {
682 return !mWebgl || mWebgl->IsContextLost();
685 // Signal to CanvasRenderingContext2D when the WebGL context is lost.
686 bool DrawTargetWebgl::IsValid() const {
687 return mSharedContext && !mSharedContext->IsContextLost();
690 already_AddRefed<DrawTargetWebgl> DrawTargetWebgl::Create(
691 const IntSize& aSize, SurfaceFormat aFormat) {
692 if (!StaticPrefs::gfx_canvas_accelerated()) {
693 return nullptr;
696 // If context initialization would fail, don't even try to create a context.
697 if (sContextInitError) {
698 return nullptr;
701 if (!Factory::AllowedSurfaceSize(aSize)) {
702 return nullptr;
705 // The interpretation of the min-size and max-size follows from the old
706 // SkiaGL prefs. First just ensure that the context is not unreasonably
707 // small.
708 static const int32_t kMinDimension = 16;
709 if (std::min(aSize.width, aSize.height) < kMinDimension) {
710 return nullptr;
713 int32_t minSize = StaticPrefs::gfx_canvas_accelerated_min_size();
714 if (aSize.width * aSize.height < minSize * minSize) {
715 return nullptr;
718 // Maximum pref allows 3 different options:
719 // 0 means unlimited size,
720 // > 0 means use value as an absolute threshold,
721 // < 0 means use the number of screen pixels as a threshold.
722 int32_t maxSize = StaticPrefs::gfx_canvas_accelerated_max_size();
723 if (maxSize > 0) {
724 if (std::max(aSize.width, aSize.height) > maxSize) {
725 return nullptr;
727 } else if (maxSize < 0) {
728 // Default to historical mobile screen size of 980x480, like FishIEtank.
729 // In addition, allow acceleration up to this size even if the screen is
730 // smaller. A lot content expects this size to work well. See Bug 999841
731 static const int32_t kScreenPixels = 980 * 480;
732 IntSize screenSize = gfxPlatform::GetPlatform()->GetScreenSize();
733 if (aSize.width * aSize.height >
734 std::max(screenSize.width * screenSize.height, kScreenPixels)) {
735 return nullptr;
739 RefPtr<DrawTargetWebgl> dt = new DrawTargetWebgl;
740 if (!dt->Init(aSize, aFormat) || !dt->IsValid()) {
741 return nullptr;
744 return dt.forget();
747 void* DrawTargetWebgl::GetNativeSurface(NativeSurfaceType aType) {
748 switch (aType) {
749 case NativeSurfaceType::WEBGL_CONTEXT:
750 // If the context is lost, then don't attempt to access it.
751 if (mSharedContext->IsContextLost()) {
752 return nullptr;
754 if (!mWebglValid) {
755 FlushFromSkia();
757 return mSharedContext->mWebgl.get();
758 default:
759 return nullptr;
763 // Wrap a WebGL texture holding a snapshot with a texture handle. Note that
764 // while the texture is still in use as the backing texture of a framebuffer,
765 // it's texture memory is not currently tracked with other texture handles.
766 // Once it is finally orphaned and used as a texture handle, it must be added
767 // to the resource usage totals.
768 already_AddRefed<TextureHandle> DrawTargetWebgl::SharedContext::WrapSnapshot(
769 const IntSize& aSize, SurfaceFormat aFormat, RefPtr<WebGLTextureJS> aTex) {
770 // Ensure there is enough space for the texture.
771 size_t usedBytes = TextureHandle::UsedBytes(aFormat, aSize);
772 PruneTextureMemory(usedBytes, false);
773 // Allocate a handle for the texture
774 RefPtr<StandaloneTexture> handle =
775 new StandaloneTexture(aSize, aFormat, aTex.forget());
776 mStandaloneTextures.push_back(handle);
777 mTextureHandles.insertFront(handle);
778 mTotalTextureMemory += usedBytes;
779 mUsedTextureMemory += usedBytes;
780 ++mNumTextureHandles;
781 return handle.forget();
784 void DrawTargetWebgl::SharedContext::SetTexFilter(WebGLTextureJS* aTex,
785 bool aFilter) {
786 mWebgl->TexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
787 aFilter ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST);
788 mWebgl->TexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
789 aFilter ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST);
792 void DrawTargetWebgl::SharedContext::InitTexParameters(WebGLTextureJS* aTex,
793 bool aFilter) {
794 mWebgl->TexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
795 LOCAL_GL_CLAMP_TO_EDGE);
796 mWebgl->TexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
797 LOCAL_GL_CLAMP_TO_EDGE);
798 SetTexFilter(aTex, aFilter);
801 // Copy the contents of the WebGL framebuffer into a WebGL texture.
802 already_AddRefed<TextureHandle> DrawTargetWebgl::SharedContext::CopySnapshot() {
803 // If the target is going away, then we can just directly reuse the
804 // framebuffer texture since it will never change.
805 RefPtr<WebGLTextureJS> tex = mWebgl->CreateTexture();
806 if (!tex) {
807 return nullptr;
810 SurfaceFormat format = mCurrentTarget->GetFormat();
811 IntSize size = mCurrentTarget->GetSize();
812 // Create a texture to hold the copy
813 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex);
814 mWebgl->TexStorage2D(LOCAL_GL_TEXTURE_2D, 1, LOCAL_GL_RGBA8, size.width,
815 size.height);
816 InitTexParameters(tex);
817 // Copy the framebuffer into the texture
818 mWebgl->CopyTexSubImage2D(LOCAL_GL_TEXTURE_2D, 0, 0, 0, 0, 0, size.width,
819 size.height);
820 ClearLastTexture();
822 return WrapSnapshot(size, format, tex.forget());
825 inline DrawTargetWebgl::AutoRestoreContext::AutoRestoreContext(
826 DrawTargetWebgl* aTarget)
827 : mTarget(aTarget),
828 mClipRect(aTarget->mSharedContext->mClipRect),
829 mLastClipMask(aTarget->mSharedContext->mLastClipMask) {}
831 inline DrawTargetWebgl::AutoRestoreContext::~AutoRestoreContext() {
832 mTarget->mSharedContext->SetClipRect(mClipRect);
833 if (mLastClipMask) {
834 mTarget->mSharedContext->SetClipMask(mLastClipMask);
836 mTarget->mRefreshClipState = true;
839 // Utility method to install the target before copying a snapshot.
840 already_AddRefed<TextureHandle> DrawTargetWebgl::CopySnapshot() {
841 AutoRestoreContext restore(this);
842 if (!PrepareContext(false)) {
843 return nullptr;
845 return mSharedContext->CopySnapshot();
848 // Borrow a snapshot that may be used by another thread for composition. Only
849 // Skia snapshots are safe to pass around.
850 already_AddRefed<SourceSurface> DrawTargetWebgl::GetDataSnapshot() {
851 if (!mSkiaValid) {
852 ReadIntoSkia();
853 } else if (mSkiaLayer) {
854 FlattenSkia();
856 return mSkia->Snapshot(mFormat);
859 already_AddRefed<SourceSurface> DrawTargetWebgl::Snapshot() {
860 // If already using the Skia fallback, then just snapshot that.
861 if (mSkiaValid) {
862 return GetDataSnapshot();
865 // There's no valid Skia snapshot, so we need to get one from the WebGL
866 // context.
867 if (!mSnapshot) {
868 // Create a copy-on-write reference to this target.
869 mSnapshot = new SourceSurfaceWebgl(this);
871 return do_AddRef(mSnapshot);
874 // Read from the WebGL context into a buffer. This handles both swizzling BGRA
875 // to RGBA and flipping the image.
876 bool DrawTargetWebgl::SharedContext::ReadInto(uint8_t* aDstData,
877 int32_t aDstStride,
878 SurfaceFormat aFormat,
879 const IntRect& aBounds,
880 TextureHandle* aHandle) {
881 MOZ_ASSERT(aFormat == SurfaceFormat::B8G8R8A8 ||
882 aFormat == SurfaceFormat::B8G8R8X8);
884 // If reading into a new texture, we have to bind it to a scratch framebuffer
885 // for reading.
886 if (aHandle) {
887 if (!mScratchFramebuffer) {
888 mScratchFramebuffer = mWebgl->CreateFramebuffer();
890 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mScratchFramebuffer);
891 mWebgl->FramebufferTexture2D(
892 LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D,
893 aHandle->GetWebGLTexture(), 0);
896 webgl::ReadPixelsDesc desc;
897 desc.srcOffset = *ivec2::From(aBounds);
898 desc.size = *uvec2::FromSize(aBounds);
899 desc.packState.rowLength = aDstStride / 4;
901 bool success = false;
902 if (mCurrentTarget && mCurrentTarget->mShmem.IsWritable() &&
903 aDstData == mCurrentTarget->mShmem.get<uint8_t>()) {
904 success = mWebgl->DoReadPixels(desc, mCurrentTarget->mShmem);
905 } else {
906 Range<uint8_t> range = {aDstData, size_t(aDstStride) * aBounds.height};
907 success = mWebgl->DoReadPixels(desc, range);
910 // Restore the actual framebuffer after reading is done.
911 if (aHandle && mCurrentTarget) {
912 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mCurrentTarget->mFramebuffer);
915 return success;
918 already_AddRefed<DataSourceSurface>
919 DrawTargetWebgl::SharedContext::ReadSnapshot(TextureHandle* aHandle) {
920 // Allocate a data surface, map it, and read from the WebGL context into the
921 // surface.
922 SurfaceFormat format = SurfaceFormat::UNKNOWN;
923 IntRect bounds;
924 if (aHandle) {
925 format = aHandle->GetFormat();
926 bounds = aHandle->GetBounds();
927 } else {
928 format = mCurrentTarget->GetFormat();
929 bounds = mCurrentTarget->GetRect();
931 RefPtr<DataSourceSurface> surface =
932 Factory::CreateDataSourceSurface(bounds.Size(), format);
933 if (!surface) {
934 return nullptr;
936 DataSourceSurface::ScopedMap dstMap(surface, DataSourceSurface::WRITE);
937 if (!dstMap.IsMapped() || !ReadInto(dstMap.GetData(), dstMap.GetStride(),
938 format, bounds, aHandle)) {
939 return nullptr;
941 return surface.forget();
944 // Utility method to install the target before reading a snapshot.
945 bool DrawTargetWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride) {
946 if (!PrepareContext(false)) {
947 return false;
950 return mSharedContext->ReadInto(aDstData, aDstStride, GetFormat(), GetRect());
953 // Utility method to install the target before reading a snapshot.
954 already_AddRefed<DataSourceSurface> DrawTargetWebgl::ReadSnapshot() {
955 AutoRestoreContext restore(this);
956 if (!PrepareContext(false)) {
957 return nullptr;
959 mProfile.OnReadback();
960 return mSharedContext->ReadSnapshot();
963 already_AddRefed<SourceSurface> DrawTargetWebgl::GetBackingSurface() {
964 return Snapshot();
967 void DrawTargetWebgl::DetachAllSnapshots() {
968 mSkia->DetachAllSnapshots();
969 ClearSnapshot();
972 // Prepare the framebuffer for accelerated drawing. Any cached snapshots will
973 // be invalidated if not detached and copied here. Ensure the WebGL
974 // framebuffer's contents are updated if still somehow stored in the Skia
975 // framebuffer.
976 bool DrawTargetWebgl::MarkChanged() {
977 if (mSnapshot) {
978 // Try to copy the target into a new texture if possible.
979 ClearSnapshot(true, true);
981 if (!mWebglValid && !FlushFromSkia()) {
982 return false;
984 mSkiaValid = false;
985 return true;
988 bool DrawTargetWebgl::LockBits(uint8_t** aData, IntSize* aSize,
989 int32_t* aStride, SurfaceFormat* aFormat,
990 IntPoint* aOrigin) {
991 // Can only access pixels if there is valid, flattened Skia data.
992 if (mSkiaValid && !mSkiaLayer) {
993 MarkSkiaChanged();
994 return mSkia->LockBits(aData, aSize, aStride, aFormat, aOrigin);
996 return false;
999 void DrawTargetWebgl::ReleaseBits(uint8_t* aData) {
1000 // Can only access pixels if there is valid, flattened Skia data.
1001 if (mSkiaValid && !mSkiaLayer) {
1002 mSkia->ReleaseBits(aData);
1006 // Format is x, y, alpha
1007 static const float kRectVertexData[12] = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
1008 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f};
1010 // Orphans the contents of the path vertex buffer. The beginning of the buffer
1011 // always contains data for a simple rectangle draw to avoid needing to switch
1012 // buffers.
1013 void DrawTargetWebgl::SharedContext::ResetPathVertexBuffer() {
1014 mWebgl->BindBuffer(LOCAL_GL_ARRAY_BUFFER, mPathVertexBuffer.get());
1015 mWebgl->RawBufferData(
1016 LOCAL_GL_ARRAY_BUFFER, nullptr,
1017 std::max(size_t(mPathVertexCapacity), sizeof(kRectVertexData)),
1018 LOCAL_GL_DYNAMIC_DRAW);
1019 mWebgl->RawBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0,
1020 (const uint8_t*)kRectVertexData,
1021 sizeof(kRectVertexData));
1022 mPathVertexOffset = sizeof(kRectVertexData);
1025 // Attempts to create all shaders and resources to be used for drawing commands.
1026 // Returns whether or not this succeeded.
1027 bool DrawTargetWebgl::SharedContext::CreateShaders() {
1028 if (!mPathVertexArray) {
1029 mPathVertexArray = mWebgl->CreateVertexArray();
1031 if (!mPathVertexBuffer) {
1032 mPathVertexBuffer = mWebgl->CreateBuffer();
1033 mWebgl->BindVertexArray(mPathVertexArray.get());
1034 ResetPathVertexBuffer();
1035 mWebgl->EnableVertexAttribArray(0);
1036 mWebgl->VertexAttribPointer(0, 3, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, 0);
1038 if (!mSolidProgram) {
1039 // AA is computed by using the basis vectors of the transform to determine
1040 // both the scale and orientation. The scale is then used to extrude the
1041 // rectangle outward by 1 screen-space pixel to account for the AA region.
1042 // The distance to the rectangle edges is passed to the fragment shader in
1043 // an interpolant, biased by 0.5 so it represents the desired coverage. The
1044 // minimum coverage is then chosen by the fragment shader to use as an AA
1045 // coverage value to modulate the color.
1046 auto vsSource =
1047 u"attribute vec3 a_vertex;\n"
1048 "uniform vec2 u_transform[3];\n"
1049 "uniform vec2 u_viewport;\n"
1050 "uniform float u_aa;\n"
1051 "varying vec2 v_cliptc;\n"
1052 "varying vec4 v_dist;\n"
1053 "varying float v_alpha;\n"
1054 "void main() {\n"
1055 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1056 " dot(u_transform[1], u_transform[1]));\n"
1057 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1058 " scale *= invScale;\n"
1059 " vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
1060 "1.0);\n"
1061 " vec2 vertex = u_transform[0] * extrude.x +\n"
1062 " u_transform[1] * extrude.y +\n"
1063 " u_transform[2];\n"
1064 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1065 " v_cliptc = vertex / u_viewport;\n"
1066 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
1067 " v_alpha = a_vertex.z;\n"
1068 "}\n"_ns;
1069 auto fsSource =
1070 u"precision mediump float;\n"
1071 "uniform vec4 u_color;\n"
1072 "uniform sampler2D u_clipmask;\n"
1073 "varying vec2 v_cliptc;\n"
1074 "varying vec4 v_dist;\n"
1075 "varying float v_alpha;\n"
1076 "void main() {\n"
1077 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1078 " vec2 dist = min(v_dist.xy, v_dist.zw);\n"
1079 " float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
1080 " gl_FragColor = clip * aa * u_color;\n"
1081 "}\n"_ns;
1082 RefPtr<WebGLShaderJS> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER);
1083 mWebgl->ShaderSource(*vsId, vsSource);
1084 mWebgl->CompileShader(*vsId);
1085 if (!mWebgl->GetCompileResult(*vsId).success) {
1086 return false;
1088 RefPtr<WebGLShaderJS> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER);
1089 mWebgl->ShaderSource(*fsId, fsSource);
1090 mWebgl->CompileShader(*fsId);
1091 if (!mWebgl->GetCompileResult(*fsId).success) {
1092 return false;
1094 mSolidProgram = mWebgl->CreateProgram();
1095 mWebgl->AttachShader(*mSolidProgram, *vsId);
1096 mWebgl->AttachShader(*mSolidProgram, *fsId);
1097 mWebgl->BindAttribLocation(*mSolidProgram, 0, u"a_vertex"_ns);
1098 mWebgl->LinkProgram(*mSolidProgram);
1099 if (!mWebgl->GetLinkResult(*mSolidProgram).success) {
1100 return false;
1102 mSolidProgramViewport =
1103 mWebgl->GetUniformLocation(*mSolidProgram, u"u_viewport"_ns);
1104 mSolidProgramAA = mWebgl->GetUniformLocation(*mSolidProgram, u"u_aa"_ns);
1105 mSolidProgramTransform =
1106 mWebgl->GetUniformLocation(*mSolidProgram, u"u_transform"_ns);
1107 mSolidProgramColor =
1108 mWebgl->GetUniformLocation(*mSolidProgram, u"u_color"_ns);
1109 mSolidProgramClipMask =
1110 mWebgl->GetUniformLocation(*mSolidProgram, u"u_clipmask"_ns);
1111 if (!mSolidProgramViewport || !mSolidProgramAA || !mSolidProgramTransform ||
1112 !mSolidProgramColor || !mSolidProgramClipMask) {
1113 return false;
1115 mWebgl->UseProgram(mSolidProgram);
1116 int32_t clipMaskData = 1;
1117 mWebgl->UniformData(LOCAL_GL_INT, mSolidProgramClipMask, false,
1118 {(const uint8_t*)&clipMaskData, sizeof(clipMaskData)});
1121 if (!mImageProgram) {
1122 auto vsSource =
1123 u"attribute vec3 a_vertex;\n"
1124 "uniform vec2 u_viewport;\n"
1125 "uniform float u_aa;\n"
1126 "uniform vec2 u_transform[3];\n"
1127 "uniform vec2 u_texmatrix[3];\n"
1128 "varying vec2 v_cliptc;\n"
1129 "varying vec2 v_texcoord;\n"
1130 "varying vec4 v_dist;\n"
1131 "varying float v_alpha;\n"
1132 "void main() {\n"
1133 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1134 " dot(u_transform[1], u_transform[1]));\n"
1135 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1136 " scale *= invScale;\n"
1137 " vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
1138 "1.0);\n"
1139 " vec2 vertex = u_transform[0] * extrude.x +\n"
1140 " u_transform[1] * extrude.y +\n"
1141 " u_transform[2];\n"
1142 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1143 " v_cliptc = vertex / u_viewport;\n"
1144 " v_texcoord = u_texmatrix[0] * extrude.x +\n"
1145 " u_texmatrix[1] * extrude.y +\n"
1146 " u_texmatrix[2];\n"
1147 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
1148 " v_alpha = a_vertex.z;\n"
1149 "}\n"_ns;
1150 auto fsSource =
1151 u"precision mediump float;\n"
1152 "uniform vec4 u_texbounds;\n"
1153 "uniform vec4 u_color;\n"
1154 "uniform float u_swizzle;\n"
1155 "uniform sampler2D u_sampler;\n"
1156 "uniform sampler2D u_clipmask;\n"
1157 "varying vec2 v_cliptc;\n"
1158 "varying vec2 v_texcoord;\n"
1159 "varying vec4 v_dist;\n"
1160 "varying float v_alpha;\n"
1161 "void main() {\n"
1162 " vec2 tc = clamp(v_texcoord, u_texbounds.xy, u_texbounds.zw);\n"
1163 " vec4 image = texture2D(u_sampler, tc);\n"
1164 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1165 " vec2 dist = min(v_dist.xy, v_dist.zw);\n"
1166 " float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
1167 " gl_FragColor = clip * aa * u_color *\n"
1168 " mix(image, image.rrrr, u_swizzle);\n"
1169 "}\n"_ns;
1170 RefPtr<WebGLShaderJS> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER);
1171 mWebgl->ShaderSource(*vsId, vsSource);
1172 mWebgl->CompileShader(*vsId);
1173 if (!mWebgl->GetCompileResult(*vsId).success) {
1174 return false;
1176 RefPtr<WebGLShaderJS> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER);
1177 mWebgl->ShaderSource(*fsId, fsSource);
1178 mWebgl->CompileShader(*fsId);
1179 if (!mWebgl->GetCompileResult(*fsId).success) {
1180 return false;
1182 mImageProgram = mWebgl->CreateProgram();
1183 mWebgl->AttachShader(*mImageProgram, *vsId);
1184 mWebgl->AttachShader(*mImageProgram, *fsId);
1185 mWebgl->BindAttribLocation(*mImageProgram, 0, u"a_vertex"_ns);
1186 mWebgl->LinkProgram(*mImageProgram);
1187 if (!mWebgl->GetLinkResult(*mImageProgram).success) {
1188 return false;
1190 mImageProgramViewport =
1191 mWebgl->GetUniformLocation(*mImageProgram, u"u_viewport"_ns);
1192 mImageProgramAA = mWebgl->GetUniformLocation(*mImageProgram, u"u_aa"_ns);
1193 mImageProgramTransform =
1194 mWebgl->GetUniformLocation(*mImageProgram, u"u_transform"_ns);
1195 mImageProgramTexMatrix =
1196 mWebgl->GetUniformLocation(*mImageProgram, u"u_texmatrix"_ns);
1197 mImageProgramTexBounds =
1198 mWebgl->GetUniformLocation(*mImageProgram, u"u_texbounds"_ns);
1199 mImageProgramSwizzle =
1200 mWebgl->GetUniformLocation(*mImageProgram, u"u_swizzle"_ns);
1201 mImageProgramColor =
1202 mWebgl->GetUniformLocation(*mImageProgram, u"u_color"_ns);
1203 mImageProgramSampler =
1204 mWebgl->GetUniformLocation(*mImageProgram, u"u_sampler"_ns);
1205 mImageProgramClipMask =
1206 mWebgl->GetUniformLocation(*mImageProgram, u"u_clipmask"_ns);
1207 if (!mImageProgramViewport || !mImageProgramAA || !mImageProgramTransform ||
1208 !mImageProgramTexMatrix || !mImageProgramTexBounds ||
1209 !mImageProgramSwizzle || !mImageProgramColor || !mImageProgramSampler ||
1210 !mImageProgramClipMask) {
1211 return false;
1213 mWebgl->UseProgram(mImageProgram);
1214 int32_t samplerData = 0;
1215 mWebgl->UniformData(LOCAL_GL_INT, mImageProgramSampler, false,
1216 {(const uint8_t*)&samplerData, sizeof(samplerData)});
1217 int32_t clipMaskData = 1;
1218 mWebgl->UniformData(LOCAL_GL_INT, mImageProgramClipMask, false,
1219 {(const uint8_t*)&clipMaskData, sizeof(clipMaskData)});
1221 return true;
1224 void DrawTargetWebgl::ClearRect(const Rect& aRect) {
1225 // OP_SOURCE may not be bounded by a mask, so we ensure that a clip is pushed
1226 // here to avoid a group being pushed for it.
1227 PushClipRect(aRect);
1228 ColorPattern pattern(
1229 DeviceColor(0.0f, 0.0f, 0.0f, IsOpaque(mFormat) ? 1.0f : 0.0f));
1230 DrawRect(aRect, pattern, DrawOptions(1.0f, CompositionOp::OP_SOURCE));
1231 PopClip();
1234 // Attempts to create the framebuffer used for drawing and also any relevant
1235 // non-shared resources. Returns whether or not this succeeded.
1236 bool DrawTargetWebgl::CreateFramebuffer() {
1237 RefPtr<ClientWebGLContext> webgl = mSharedContext->mWebgl;
1238 if (!mFramebuffer) {
1239 mFramebuffer = webgl->CreateFramebuffer();
1241 if (!mTex) {
1242 mTex = webgl->CreateTexture();
1243 webgl->BindTexture(LOCAL_GL_TEXTURE_2D, mTex);
1244 webgl->TexStorage2D(LOCAL_GL_TEXTURE_2D, 1, LOCAL_GL_RGBA8, mSize.width,
1245 mSize.height);
1246 mSharedContext->InitTexParameters(mTex);
1247 webgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mFramebuffer);
1248 webgl->FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
1249 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D,
1250 mTex, 0);
1251 webgl->Viewport(0, 0, mSize.width, mSize.height);
1252 webgl->ClearColor(0.0f, 0.0f, 0.0f, IsOpaque(mFormat) ? 1.0f : 0.0f);
1253 webgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT);
1254 mSharedContext->ClearTarget();
1255 mSharedContext->ClearLastTexture();
1257 return true;
1260 void DrawTargetWebgl::CopySurface(SourceSurface* aSurface,
1261 const IntRect& aSourceRect,
1262 const IntPoint& aDestination) {
1263 if (mSkiaValid) {
1264 if (mSkiaLayer) {
1265 if (IntRect(aDestination, aSourceRect.Size()).Contains(GetRect())) {
1266 // If the the destination would override the entire layer, discard the
1267 // layer.
1268 mSkiaLayer = false;
1269 } else if (!IsOpaque(aSurface->GetFormat())) {
1270 // If the surface is not opaque, copying it into the layer results in
1271 // unintended blending rather than a copy to the destination.
1272 FlattenSkia();
1274 } else {
1275 // If there is no layer, copying is safe.
1276 MarkSkiaChanged();
1278 mSkia->CopySurface(aSurface, aSourceRect, aDestination);
1279 return;
1282 Matrix matrix = Matrix::Translation(aDestination - aSourceRect.TopLeft());
1283 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, matrix);
1284 DrawRect(Rect(IntRect(aDestination, aSourceRect.Size())), pattern,
1285 DrawOptions(1.0f, CompositionOp::OP_SOURCE), Nothing(), nullptr,
1286 false, false);
1289 void DrawTargetWebgl::PushClip(const Path* aPath) {
1290 mClipChanged = true;
1291 mRefreshClipState = true;
1292 mSkia->PushClip(aPath);
1294 mClipStack.push_back({GetTransform(), Rect(), aPath});
1297 void DrawTargetWebgl::PushClipRect(const Rect& aRect) {
1298 mClipChanged = true;
1299 mRefreshClipState = true;
1300 mSkia->PushClipRect(aRect);
1302 mClipStack.push_back({GetTransform(), aRect, nullptr});
1305 void DrawTargetWebgl::PushDeviceSpaceClipRects(const IntRect* aRects,
1306 uint32_t aCount) {
1307 mClipChanged = true;
1308 mRefreshClipState = true;
1309 mSkia->PushDeviceSpaceClipRects(aRects, aCount);
1311 for (uint32_t i = 0; i < aCount; i++) {
1312 mClipStack.push_back({Matrix(), Rect(aRects[i]), nullptr});
1316 void DrawTargetWebgl::PopClip() {
1317 mClipChanged = true;
1318 mRefreshClipState = true;
1319 mSkia->PopClip();
1321 mClipStack.pop_back();
1324 // Whether a given composition operator can be mapped to a WebGL blend mode.
1325 static inline bool SupportsDrawOptions(const DrawOptions& aOptions) {
1326 switch (aOptions.mCompositionOp) {
1327 case CompositionOp::OP_OVER:
1328 case CompositionOp::OP_ADD:
1329 case CompositionOp::OP_ATOP:
1330 case CompositionOp::OP_SOURCE:
1331 return true;
1332 default:
1333 return false;
1337 // Whether a pattern can be mapped to an available WebGL shader.
1338 bool DrawTargetWebgl::SharedContext::SupportsPattern(const Pattern& aPattern) {
1339 switch (aPattern.GetType()) {
1340 case PatternType::COLOR:
1341 return true;
1342 case PatternType::SURFACE: {
1343 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1344 if (surfacePattern.mExtendMode != ExtendMode::CLAMP) {
1345 return false;
1347 if (surfacePattern.mSurface) {
1348 IntSize size = surfacePattern.mSurface->GetSize();
1349 // The maximum size a surface can be before triggering a fallback to
1350 // software. Bound the maximum surface size by the actual texture size
1351 // limit.
1352 int32_t maxSize = int32_t(
1353 std::min(StaticPrefs::gfx_canvas_accelerated_max_surface_size(),
1354 mMaxTextureSize));
1355 // Check if either of the surface dimensions or the sampling rect,
1356 // if supplied, exceed the maximum.
1357 if (std::max(size.width, size.height) > maxSize &&
1358 (surfacePattern.mSamplingRect.IsEmpty() ||
1359 std::max(surfacePattern.mSamplingRect.width,
1360 surfacePattern.mSamplingRect.height) > maxSize)) {
1361 return false;
1364 return true;
1366 default:
1367 // Patterns other than colors and surfaces are currently not accelerated.
1368 return false;
1372 // Whether a given composition operator is associative and thus allows drawing
1373 // into a separate layer that can be later composited back into the WebGL
1374 // context.
1375 static inline bool SupportsLayering(const DrawOptions& aOptions) {
1376 switch (aOptions.mCompositionOp) {
1377 case CompositionOp::OP_OVER:
1378 // Layering is only supported for the default source-over composition op.
1379 return true;
1380 default:
1381 return false;
1385 // When a texture handle is no longer referenced, it must mark itself unused
1386 // by unlinking its owning surface.
1387 static void ReleaseTextureHandle(void* aPtr) {
1388 static_cast<TextureHandle*>(aPtr)->SetSurface(nullptr);
1391 bool DrawTargetWebgl::DrawRect(const Rect& aRect, const Pattern& aPattern,
1392 const DrawOptions& aOptions,
1393 Maybe<DeviceColor> aMaskColor,
1394 RefPtr<TextureHandle>* aHandle,
1395 bool aTransformed, bool aClipped,
1396 bool aAccelOnly, bool aForceUpdate,
1397 const StrokeOptions* aStrokeOptions) {
1398 // If there is nothing to draw, then don't draw...
1399 if (aRect.IsEmpty()) {
1400 return true;
1403 // If we're already drawing directly to the WebGL context, then we want to
1404 // continue to do so. However, if we're drawing into a Skia layer over the
1405 // WebGL context, then we need to be careful to avoid repeatedly clearing
1406 // and flushing the layer if we hit a drawing request that can be accelerated
1407 // in between layered drawing requests, as clearing and flushing the layer
1408 // can be significantly expensive when repeated. So when a Skia layer is
1409 // active, if it is possible to continue drawing into the layer, then don't
1410 // accelerate the drawing request.
1411 if (mWebglValid || (mSkiaLayer && !mLayerDepth &&
1412 (aAccelOnly || !SupportsLayering(aOptions)))) {
1413 // If we get here, either the WebGL context is being directly drawn to
1414 // or we are going to flush the Skia layer to it before doing so. The shared
1415 // context still needs to be claimed and prepared for drawing. If this
1416 // fails, we just fall back to drawing with Skia below.
1417 if (PrepareContext(aClipped)) {
1418 // The shared context is claimed and the framebuffer is now valid, so try
1419 // accelerated drawing.
1420 return mSharedContext->DrawRectAccel(
1421 aRect, aPattern, aOptions, aMaskColor, aHandle, aTransformed,
1422 aClipped, aAccelOnly, aForceUpdate, aStrokeOptions);
1426 // Either there is no valid WebGL target to draw into, or we failed to prepare
1427 // it for drawing. The only thing we can do at this point is fall back to
1428 // drawing with Skia. If the request explicitly requires accelerated drawing,
1429 // then draw nothing before returning failure.
1430 if (!aAccelOnly) {
1431 DrawRectFallback(aRect, aPattern, aOptions, aMaskColor, aTransformed,
1432 aClipped, aStrokeOptions);
1434 return false;
1437 void DrawTargetWebgl::DrawRectFallback(const Rect& aRect,
1438 const Pattern& aPattern,
1439 const DrawOptions& aOptions,
1440 Maybe<DeviceColor> aMaskColor,
1441 bool aTransformed, bool aClipped,
1442 const StrokeOptions* aStrokeOptions) {
1443 // Invalidate the WebGL target and prepare the Skia target for drawing.
1444 MarkSkiaChanged(aOptions);
1446 if (aTransformed) {
1447 // If transforms are requested, then just translate back to FillRect.
1448 if (aMaskColor) {
1449 mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions);
1450 } else if (aStrokeOptions) {
1451 mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions);
1452 } else {
1453 mSkia->FillRect(aRect, aPattern, aOptions);
1455 } else if (aClipped) {
1456 // If no transform was requested but clipping is still required, then
1457 // temporarily reset the transform before translating to FillRect.
1458 mSkia->SetTransform(Matrix());
1459 if (aMaskColor) {
1460 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1461 if (surfacePattern.mSamplingRect.IsEmpty()) {
1462 mSkia->MaskSurface(ColorPattern(*aMaskColor), surfacePattern.mSurface,
1463 aRect.TopLeft(), aOptions);
1464 } else {
1465 mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions);
1467 } else if (aStrokeOptions) {
1468 mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions);
1469 } else {
1470 mSkia->FillRect(aRect, aPattern, aOptions);
1472 mSkia->SetTransform(mTransform);
1473 } else if (aPattern.GetType() == PatternType::SURFACE) {
1474 // No transform nor clipping was requested, so it is essentially just a
1475 // copy.
1476 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1477 mSkia->CopySurface(surfacePattern.mSurface,
1478 surfacePattern.mSurface->GetRect(),
1479 IntPoint::Round(aRect.TopLeft()));
1480 } else {
1481 MOZ_ASSERT(false);
1485 inline already_AddRefed<WebGLTextureJS>
1486 DrawTargetWebgl::SharedContext::GetCompatibleSnapshot(SourceSurface* aSurface) {
1487 if (aSurface->GetType() == SurfaceType::WEBGL) {
1488 RefPtr<SourceSurfaceWebgl> webglSurf =
1489 static_cast<SourceSurfaceWebgl*>(aSurface);
1490 if (this == webglSurf->mSharedContext) {
1491 // If there is a snapshot copy in a texture handle, use that.
1492 if (webglSurf->mHandle) {
1493 return do_AddRef(webglSurf->mHandle->GetWebGLTexture());
1495 if (RefPtr<DrawTargetWebgl> webglDT = webglSurf->GetTarget()) {
1496 // If there is a copy-on-write reference to a target, use its backing
1497 // texture directly. This is only safe if the targets don't match, but
1498 // MarkChanged should ensure that any snapshots were copied into a
1499 // texture handle before we ever get here.
1500 if (!IsCurrentTarget(webglDT)) {
1501 return do_AddRef(webglDT->mTex);
1506 return nullptr;
1509 bool DrawTargetWebgl::SharedContext::UploadSurface(DataSourceSurface* aData,
1510 SurfaceFormat aFormat,
1511 const IntRect& aSrcRect,
1512 const IntPoint& aDstOffset,
1513 bool aInit, bool aZero) {
1514 webgl::TexUnpackBlobDesc texDesc = {
1515 LOCAL_GL_TEXTURE_2D,
1516 {uint32_t(aSrcRect.width), uint32_t(aSrcRect.height), 1}};
1517 if (aData) {
1518 // The surface needs to be uploaded to its backing texture either to
1519 // initialize or update the texture handle contents. Map the data
1520 // contents of the surface so it can be read.
1521 DataSourceSurface::ScopedMap map(aData, DataSourceSurface::READ);
1522 if (!map.IsMapped()) {
1523 return false;
1525 int32_t stride = map.GetStride();
1526 int32_t bpp = BytesPerPixel(aFormat);
1527 if (mCurrentTarget && mCurrentTarget->mShmem.IsWritable() &&
1528 map.GetData() == mCurrentTarget->mShmem.get<uint8_t>()) {
1529 texDesc.sd = Some(layers::SurfaceDescriptorBuffer(
1530 layers::RGBDescriptor(mCurrentTarget->mSize, SurfaceFormat::R8G8B8A8),
1531 mCurrentTarget->mShmem));
1532 texDesc.structuredSrcSize =
1533 uvec2::From(stride / bpp, mCurrentTarget->mSize.height);
1534 texDesc.unpacking.skipPixels = aSrcRect.x;
1535 texDesc.unpacking.skipRows = aSrcRect.y;
1536 mWaitForShmem = true;
1537 } else {
1538 // Get the data pointer range considering the sampling rect offset and
1539 // size.
1540 Range<const uint8_t> range(
1541 map.GetData() + aSrcRect.y * size_t(stride) + aSrcRect.x * bpp,
1542 std::max(aSrcRect.height - 1, 0) * size_t(stride) +
1543 aSrcRect.width * bpp);
1544 texDesc.cpuData = Some(RawBuffer(range));
1546 // If the stride happens to be 4 byte aligned, assume that is the
1547 // desired alignment regardless of format (even A8). Otherwise, we
1548 // default to byte alignment.
1549 texDesc.unpacking.alignmentInTypeElems = stride % 4 ? 1 : 4;
1550 texDesc.unpacking.rowLength = stride / bpp;
1551 } else if (aZero) {
1552 // Create a PBO filled with zero data to initialize the texture data and
1553 // avoid slow initialization inside WebGL.
1554 MOZ_ASSERT(aSrcRect.TopLeft() == IntPoint(0, 0));
1555 size_t size =
1556 size_t(GetAlignedStride<4>(aSrcRect.width, BytesPerPixel(aFormat))) *
1557 aSrcRect.height;
1558 if (!mZeroBuffer || size > mZeroSize) {
1559 mZeroBuffer = mWebgl->CreateBuffer();
1560 mZeroSize = size;
1561 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mZeroBuffer);
1562 // WebGL will zero initialize the empty buffer, so we don't send zero data
1563 // explicitly.
1564 mWebgl->RawBufferData(LOCAL_GL_PIXEL_UNPACK_BUFFER, nullptr, size,
1565 LOCAL_GL_STATIC_DRAW);
1566 } else {
1567 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mZeroBuffer);
1569 texDesc.pboOffset = Some(0);
1571 // Upload as RGBA8 to avoid swizzling during upload. Surfaces provide
1572 // data as BGRA, but we manually swizzle that in the shader. An A8
1573 // surface will be stored as an R8 texture that will also be swizzled
1574 // in the shader.
1575 GLenum intFormat =
1576 aFormat == SurfaceFormat::A8 ? LOCAL_GL_R8 : LOCAL_GL_RGBA8;
1577 GLenum extFormat =
1578 aFormat == SurfaceFormat::A8 ? LOCAL_GL_RED : LOCAL_GL_RGBA;
1579 webgl::PackingInfo texPI = {extFormat, LOCAL_GL_UNSIGNED_BYTE};
1580 // Do the (partial) upload for the shared or standalone texture.
1581 mWebgl->RawTexImage(0, aInit ? intFormat : 0,
1582 {uint32_t(aDstOffset.x), uint32_t(aDstOffset.y), 0},
1583 texPI, std::move(texDesc));
1584 if (!aData && aZero) {
1585 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
1587 return true;
1590 static inline SamplingFilter GetSamplingFilter(const Pattern& aPattern) {
1591 return aPattern.GetType() == PatternType::SURFACE
1592 ? static_cast<const SurfacePattern&>(aPattern).mSamplingFilter
1593 : SamplingFilter::GOOD;
1596 static inline bool UseNearestFilter(const Pattern& aPattern) {
1597 return GetSamplingFilter(aPattern) == SamplingFilter::POINT;
1600 // Determine if the rectangle is still axis-aligned and pixel-aligned.
1601 static inline Maybe<IntRect> IsAlignedRect(bool aTransformed,
1602 const Matrix& aCurrentTransform,
1603 const Rect& aRect) {
1604 if (!aTransformed || aCurrentTransform.HasOnlyIntegerTranslation()) {
1605 auto intRect = RoundedToInt(aRect);
1606 if (aRect.WithinEpsilonOf(Rect(intRect), 1.0e-3f)) {
1607 if (aTransformed) {
1608 intRect += RoundedToInt(aCurrentTransform.GetTranslation());
1610 return Some(intRect);
1613 return Nothing();
1616 // Common rectangle and pattern drawing function shared by many DrawTarget
1617 // commands. If aMaskColor is specified, the provided surface pattern will be
1618 // treated as a mask. If aHandle is specified, then the surface pattern's
1619 // texture will be cached in the supplied handle, as opposed to using the
1620 // surface's user data. If aTransformed or aClipped are false, then transforms
1621 // and/or clipping will be disabled. If aAccelOnly is specified, then this
1622 // function will return before it would have otherwise drawn without
1623 // acceleration. If aForceUpdate is specified, then the provided texture handle
1624 // will be respecified with the provided surface.
1625 bool DrawTargetWebgl::SharedContext::DrawRectAccel(
1626 const Rect& aRect, const Pattern& aPattern, const DrawOptions& aOptions,
1627 Maybe<DeviceColor> aMaskColor, RefPtr<TextureHandle>* aHandle,
1628 bool aTransformed, bool aClipped, bool aAccelOnly, bool aForceUpdate,
1629 const StrokeOptions* aStrokeOptions, const PathVertexRange* aVertexRange) {
1630 // If the rect or clip rect is empty, then there is nothing to draw.
1631 if (aRect.IsEmpty() || mClipRect.IsEmpty()) {
1632 return true;
1635 // Check if the drawing options and the pattern support acceleration. Also
1636 // ensure the framebuffer is prepared for drawing. If not, fall back to using
1637 // the Skia target.
1638 if (!SupportsDrawOptions(aOptions) || !SupportsPattern(aPattern) ||
1639 !mCurrentTarget->MarkChanged()) {
1640 // If only accelerated drawing was requested, bail out without software
1641 // drawing fallback.
1642 if (!aAccelOnly) {
1643 MOZ_ASSERT(!aVertexRange);
1644 mCurrentTarget->DrawRectFallback(aRect, aPattern, aOptions, aMaskColor,
1645 aTransformed, aClipped, aStrokeOptions);
1647 return false;
1650 const Matrix& currentTransform = GetTransform();
1652 if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE &&
1653 ((aClipped && HasClipMask()) ||
1654 !IsAlignedRect(aTransformed, currentTransform, aRect)) &&
1655 aPattern.GetType() == PatternType::SURFACE) {
1656 // We must emulate the source op for non-opaque surface patterns to avoid
1657 // using dual-source blending. This requires clearing the clip region
1658 // without using the surface's alpha before we actually add on the surface
1659 // to the destination. For opaque surfaces, we can simply use the over op.
1660 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1661 CompositionOp op = CompositionOp::OP_OVER;
1662 if (!surfacePattern.mSurface ||
1663 !IsOpaque(surfacePattern.mSurface->GetFormat()) ||
1664 aOptions.mAlpha != 1.0f) {
1665 op = CompositionOp::OP_ADD;
1666 if (DrawRectAccel(aRect, ColorPattern(DeviceColor(0, 0, 0, 0)),
1667 DrawOptions(1.0f, CompositionOp::OP_SOURCE,
1668 aOptions.mAntialiasMode),
1669 Nothing(), nullptr, aTransformed, aClipped, aAccelOnly,
1670 aForceUpdate, aStrokeOptions, aVertexRange)) {
1671 return false;
1674 return DrawRectAccel(
1675 aRect, aPattern,
1676 DrawOptions(aOptions.mAlpha, op, aOptions.mAntialiasMode), aMaskColor,
1677 aHandle, aTransformed, aClipped, aAccelOnly, aForceUpdate,
1678 aStrokeOptions, aVertexRange);
1681 // Set up the scissor test to reflect the clipping rectangle, if supplied.
1682 bool scissor = false;
1683 if (!mClipRect.Contains(IntRect(IntPoint(), mViewportSize))) {
1684 scissor = true;
1685 mWebgl->Enable(LOCAL_GL_SCISSOR_TEST);
1686 mWebgl->Scissor(mClipRect.x, mClipRect.y, mClipRect.width,
1687 mClipRect.height);
1690 bool success = false;
1692 // Now try to actually draw the pattern...
1693 switch (aPattern.GetType()) {
1694 case PatternType::COLOR: {
1695 if (!aVertexRange) {
1696 // Only an uncached draw if not using the vertex cache.
1697 mCurrentTarget->mProfile.OnUncachedDraw();
1699 auto color = static_cast<const ColorPattern&>(aPattern).mColor;
1700 float a = color.a * aOptions.mAlpha;
1701 DeviceColor premulColor(color.r * a, color.g * a, color.b * a, a);
1702 if (((a == 1.0f && aOptions.mCompositionOp == CompositionOp::OP_OVER) ||
1703 aOptions.mCompositionOp == CompositionOp::OP_SOURCE) &&
1704 !aStrokeOptions && !aVertexRange && !HasClipMask()) {
1705 // Certain color patterns can be mapped to scissored clears. The
1706 // composition op must effectively overwrite the destination, and the
1707 // transform must map to an axis-aligned integer rectangle.
1708 if (Maybe<IntRect> intRect =
1709 IsAlignedRect(aTransformed, currentTransform, aRect)) {
1710 if (!intRect->Contains(mClipRect)) {
1711 scissor = true;
1712 mWebgl->Enable(LOCAL_GL_SCISSOR_TEST);
1713 auto scissorRect = intRect->Intersect(mClipRect);
1714 mWebgl->Scissor(scissorRect.x, scissorRect.y, scissorRect.width,
1715 scissorRect.height);
1717 mWebgl->ClearColor(premulColor.b, premulColor.g, premulColor.r,
1718 premulColor.a);
1719 mWebgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT);
1720 success = true;
1721 break;
1724 // Map the composition op to a WebGL blend mode, if possible.
1725 Maybe<DeviceColor> blendColor;
1726 if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE) {
1727 // The source operator can support clipping and AA by emulating it with
1728 // the over op. Supply the color with blend state, and set the shader
1729 // color to white, to avoid needing dual-source blending.
1730 blendColor = Some(premulColor);
1731 premulColor = DeviceColor(1, 1, 1, 1);
1733 SetBlendState(aOptions.mCompositionOp, blendColor);
1734 // Since it couldn't be mapped to a scissored clear, we need to use the
1735 // solid color shader with supplied transform.
1736 if (mLastProgram != mSolidProgram) {
1737 mWebgl->UseProgram(mSolidProgram);
1738 mLastProgram = mSolidProgram;
1739 // Ensure viewport and AA state is current.
1740 mDirtyViewport = true;
1741 mDirtyAA = true;
1743 if (mDirtyViewport) {
1744 float viewportData[2] = {float(mViewportSize.width),
1745 float(mViewportSize.height)};
1746 mWebgl->UniformData(
1747 LOCAL_GL_FLOAT_VEC2, mSolidProgramViewport, false,
1748 {(const uint8_t*)viewportData, sizeof(viewportData)});
1749 mDirtyViewport = false;
1751 if (mDirtyAA || aStrokeOptions || aVertexRange) {
1752 // Native lines use line smoothing. Generated paths provide their own
1753 // AA as vertex alpha.
1754 float aaData = aStrokeOptions || aVertexRange ? 0.0f : 1.0f;
1755 mWebgl->UniformData(LOCAL_GL_FLOAT, mSolidProgramAA, false,
1756 {(const uint8_t*)&aaData, sizeof(aaData)});
1757 mDirtyAA = aaData == 0.0f;
1759 float colorData[4] = {premulColor.b, premulColor.g, premulColor.r,
1760 premulColor.a};
1761 Matrix xform(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y);
1762 if (aTransformed) {
1763 xform *= currentTransform;
1765 float xformData[6] = {xform._11, xform._12, xform._21,
1766 xform._22, xform._31, xform._32};
1767 mWebgl->UniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramTransform, false,
1768 {(const uint8_t*)xformData, sizeof(xformData)});
1769 mWebgl->UniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramColor, false,
1770 {(const uint8_t*)colorData, sizeof(colorData)});
1771 // Finally draw the colored rectangle.
1772 if (aVertexRange) {
1773 // If there's a vertex range, then we need to draw triangles within from
1774 // generated from a path stored in the path vertex buffer.
1775 mWebgl->DrawArrays(LOCAL_GL_TRIANGLES, GLint(aVertexRange->mOffset),
1776 GLsizei(aVertexRange->mLength));
1777 } else {
1778 // Otherwise we're drawing a simple stroked/filled rectangle.
1779 mWebgl->DrawArrays(
1780 aStrokeOptions ? LOCAL_GL_LINE_LOOP : LOCAL_GL_TRIANGLE_FAN, 0, 4);
1782 success = true;
1783 break;
1785 case PatternType::SURFACE: {
1786 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1787 // If a texture handle was supplied, or if the surface already has an
1788 // assigned texture handle stashed in its used data, try to use it.
1789 RefPtr<TextureHandle> handle =
1790 aHandle ? aHandle->get()
1791 : (surfacePattern.mSurface
1792 ? static_cast<TextureHandle*>(
1793 surfacePattern.mSurface->GetUserData(
1794 &mTextureHandleKey))
1795 : nullptr);
1796 IntSize texSize;
1797 IntPoint offset;
1798 SurfaceFormat format;
1799 // Check if the found handle is still valid and if its sampling rect
1800 // matches the requested sampling rect.
1801 if (handle && handle->IsValid() &&
1802 (surfacePattern.mSamplingRect.IsEmpty() ||
1803 handle->GetSamplingRect().IsEqualEdges(
1804 surfacePattern.mSamplingRect))) {
1805 texSize = handle->GetSize();
1806 format = handle->GetFormat();
1807 offset = handle->GetSamplingOffset();
1808 } else {
1809 // Otherwise, there is no handle that can be used yet, so extract
1810 // information from the surface pattern.
1811 handle = nullptr;
1812 if (!surfacePattern.mSurface) {
1813 // If there was no actual surface supplied, then we tried to draw
1814 // using a texture handle, but the texture handle wasn't valid.
1815 break;
1817 texSize = surfacePattern.mSurface->GetSize();
1818 format = surfacePattern.mSurface->GetFormat();
1819 if (!surfacePattern.mSamplingRect.IsEmpty()) {
1820 texSize = surfacePattern.mSamplingRect.Size();
1821 offset = surfacePattern.mSamplingRect.TopLeft();
1825 // We need to be able to transform from local space into texture space.
1826 Matrix invMatrix = surfacePattern.mMatrix;
1827 if (!invMatrix.Invert()) {
1828 break;
1831 RefPtr<WebGLTextureJS> tex;
1832 IntRect bounds;
1833 IntSize backingSize;
1834 RefPtr<DataSourceSurface> data;
1835 bool init = false;
1836 if (handle) {
1837 if (aForceUpdate) {
1838 data = surfacePattern.mSurface->GetDataSurface();
1839 if (!data) {
1840 break;
1842 // The size of the texture may change if we update contents.
1843 mUsedTextureMemory -= handle->UsedBytes();
1844 handle->UpdateSize(texSize);
1845 mUsedTextureMemory += handle->UsedBytes();
1846 handle->SetSamplingOffset(surfacePattern.mSamplingRect.TopLeft());
1848 // If using an existing handle, move it to the front of the MRU list.
1849 handle->remove();
1850 mTextureHandles.insertFront(handle);
1851 } else if ((tex = GetCompatibleSnapshot(surfacePattern.mSurface))) {
1852 backingSize = surfacePattern.mSurface->GetSize();
1853 bounds = IntRect(offset, texSize);
1854 // Count reusing a snapshot texture (no readback) as a cache hit.
1855 mCurrentTarget->mProfile.OnCacheHit();
1856 } else {
1857 // If we get here, we need a data surface for a texture upload.
1858 data = surfacePattern.mSurface->GetDataSurface();
1859 if (!data) {
1860 break;
1862 // There is no existing handle. Calculate the bytes that would be used
1863 // by this texture, and prune enough other textures to ensure we have
1864 // that much usable texture space available to allocate.
1865 size_t usedBytes = TextureHandle::UsedBytes(format, texSize);
1866 PruneTextureMemory(usedBytes, false);
1867 // The requested page size for shared textures.
1868 int32_t pageSize = int32_t(
1869 std::min(StaticPrefs::gfx_canvas_accelerated_shared_page_size(),
1870 mMaxTextureSize));
1871 if (!aForceUpdate &&
1872 std::max(texSize.width, texSize.height) <= pageSize / 2) {
1873 // Ensure that the surface size won't change via forced update and
1874 // that the surface is no bigger than a quadrant of a shared texture
1875 // page. If so, try to allocate it to a shared texture. Look for any
1876 // existing shared texture page with a matching format and allocate
1877 // from that if possible.
1878 for (auto& shared : mSharedTextures) {
1879 if (shared->GetFormat() == format) {
1880 bool wasEmpty = !shared->HasAllocatedHandles();
1881 handle = shared->Allocate(texSize);
1882 if (handle) {
1883 if (wasEmpty) {
1884 // If the page was previously empty, then deduct it from the
1885 // empty memory reserves.
1886 mEmptyTextureMemory -= shared->UsedBytes();
1888 break;
1892 // If we couldn't find an existing shared texture page with matching
1893 // format, then allocate a new page to put the request in.
1894 if (!handle) {
1895 tex = mWebgl->CreateTexture();
1896 if (!tex) {
1897 MOZ_ASSERT(false);
1898 break;
1900 RefPtr<SharedTexture> shared =
1901 new SharedTexture(IntSize(pageSize, pageSize), format, tex);
1902 mSharedTextures.push_back(shared);
1903 mTotalTextureMemory += shared->UsedBytes();
1904 handle = shared->Allocate(texSize);
1905 if (!handle) {
1906 MOZ_ASSERT(false);
1907 break;
1909 init = true;
1911 } else {
1912 // The surface wouldn't fit in a shared texture page, so we need to
1913 // allocate a standalone texture for it instead.
1914 tex = mWebgl->CreateTexture();
1915 if (!tex) {
1916 MOZ_ASSERT(false);
1917 break;
1919 RefPtr<StandaloneTexture> standalone =
1920 new StandaloneTexture(texSize, format, tex);
1921 mStandaloneTextures.push_back(standalone);
1922 mTotalTextureMemory += standalone->UsedBytes();
1923 handle = standalone;
1924 init = true;
1927 // Insert the new texture handle into the front of the MRU list and
1928 // update used space for it.
1929 mTextureHandles.insertFront(handle);
1930 ++mNumTextureHandles;
1931 mUsedTextureMemory += handle->UsedBytes();
1932 // Link the handle to the surface's user data.
1933 handle->SetSamplingOffset(surfacePattern.mSamplingRect.TopLeft());
1934 if (aHandle) {
1935 *aHandle = handle;
1936 } else {
1937 handle->SetSurface(surfacePattern.mSurface);
1938 surfacePattern.mSurface->AddUserData(&mTextureHandleKey, handle.get(),
1939 ReleaseTextureHandle);
1943 // Map the composition op to a WebGL blend mode, if possible. If there is
1944 // a mask color and a texture with multiple channels, assume subpixel
1945 // blending. If we encounter the source op here, then assume the surface
1946 // is opaque (non-opaque is handled above) and emulate it with over.
1947 SetBlendState(aOptions.mCompositionOp,
1948 format != SurfaceFormat::A8 ? aMaskColor : Nothing());
1949 // Switch to the image shader and set up relevant transforms.
1950 if (mLastProgram != mImageProgram) {
1951 mWebgl->UseProgram(mImageProgram);
1952 mLastProgram = mImageProgram;
1953 // Ensure viewport and AA state is current.
1954 mDirtyViewport = true;
1955 mDirtyAA = true;
1957 if (mDirtyViewport) {
1958 float viewportData[2] = {float(mViewportSize.width),
1959 float(mViewportSize.height)};
1960 mWebgl->UniformData(
1961 LOCAL_GL_FLOAT_VEC2, mImageProgramViewport, false,
1962 {(const uint8_t*)viewportData, sizeof(viewportData)});
1963 mDirtyViewport = false;
1965 if (mDirtyAA || aStrokeOptions || aVertexRange) {
1966 // AA is not supported for OP_SOURCE. Native lines use line smoothing.
1967 // Generated paths provide their own AA as vertex alpha.
1969 float aaData = mLastCompositionOp == CompositionOp::OP_SOURCE ||
1970 aStrokeOptions || aVertexRange
1971 ? 0.0f
1972 : 1.0f;
1973 mWebgl->UniformData(LOCAL_GL_FLOAT, mImageProgramAA, false,
1974 {(const uint8_t*)&aaData, sizeof(aaData)});
1975 mDirtyAA = aaData == 0.0f;
1977 DeviceColor color = aMaskColor && format != SurfaceFormat::A8
1978 ? DeviceColor::Mask(1.0f, aMaskColor->a)
1979 : aMaskColor.valueOr(DeviceColor(1, 1, 1, 1));
1980 float a = color.a * aOptions.mAlpha;
1981 float colorData[4] = {color.b * a, color.g * a, color.r * a, a};
1982 float swizzleData =
1983 aMaskColor && format == SurfaceFormat::A8 ? 1.0f : 0.0f;
1984 Matrix xform(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y);
1985 if (aTransformed) {
1986 xform *= currentTransform;
1988 float xformData[6] = {xform._11, xform._12, xform._21,
1989 xform._22, xform._31, xform._32};
1990 mWebgl->UniformData(LOCAL_GL_FLOAT_VEC2, mImageProgramTransform, false,
1991 {(const uint8_t*)xformData, sizeof(xformData)});
1992 mWebgl->UniformData(LOCAL_GL_FLOAT_VEC4, mImageProgramColor, false,
1993 {(const uint8_t*)colorData, sizeof(colorData)});
1994 mWebgl->UniformData(LOCAL_GL_FLOAT, mImageProgramSwizzle, false,
1995 {(const uint8_t*)&swizzleData, sizeof(swizzleData)});
1997 // Start binding the WebGL state for the texture.
1998 if (handle) {
1999 if (!tex) {
2000 tex = handle->GetWebGLTexture();
2002 bounds = handle->GetBounds();
2003 backingSize = handle->GetBackingSize();
2005 if (mLastTexture != tex) {
2006 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex);
2007 mLastTexture = tex;
2010 if (init) {
2011 // If this is the first time the texture is used, we need to initialize
2012 // the clamping and filtering state.
2013 InitTexParameters(tex);
2014 if (texSize != backingSize) {
2015 // If this is a shared texture handle whose actual backing texture is
2016 // larger than it, then we need to allocate the texture page to the
2017 // full backing size before we can do a partial upload of the surface.
2018 UploadSurface(nullptr, format, IntRect(IntPoint(), backingSize),
2019 IntPoint(), true, true);
2023 if (data) {
2024 UploadSurface(data, format, IntRect(offset, texSize), bounds.TopLeft(),
2025 texSize == backingSize);
2026 // Signal that we had to upload new data to the texture cache.
2027 mCurrentTarget->mProfile.OnCacheMiss();
2028 } else {
2029 // Signal that we are reusing data from the texture cache.
2030 mCurrentTarget->mProfile.OnCacheHit();
2033 // Set up the texture coordinate matrix to map from the input rectangle to
2034 // the backing texture subrect.
2035 Size backingSizeF(backingSize);
2036 Matrix uvMatrix(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y);
2037 uvMatrix *= invMatrix;
2038 uvMatrix *= Matrix(1.0f / backingSizeF.width, 0.0f, 0.0f,
2039 1.0f / backingSizeF.height,
2040 float(bounds.x - offset.x) / backingSizeF.width,
2041 float(bounds.y - offset.y) / backingSizeF.height);
2042 float uvData[6] = {uvMatrix._11, uvMatrix._12, uvMatrix._21,
2043 uvMatrix._22, uvMatrix._31, uvMatrix._32};
2044 mWebgl->UniformData(LOCAL_GL_FLOAT_VEC2, mImageProgramTexMatrix, false,
2045 {(const uint8_t*)uvData, sizeof(uvData)});
2047 // Clamp sampling to within the bounds of the backing texture subrect.
2048 float texBounds[4] = {
2049 (bounds.x + 0.5f) / backingSizeF.width,
2050 (bounds.y + 0.5f) / backingSizeF.height,
2051 (bounds.XMost() - 0.5f) / backingSizeF.width,
2052 (bounds.YMost() - 0.5f) / backingSizeF.height,
2054 mWebgl->UniformData(LOCAL_GL_FLOAT_VEC4, mImageProgramTexBounds, false,
2055 {(const uint8_t*)texBounds, sizeof(texBounds)});
2057 // Ensure we use nearest filtering when no antialiasing is requested.
2058 if (UseNearestFilter(surfacePattern)) {
2059 SetTexFilter(tex, false);
2062 // Finally draw the image rectangle.
2063 if (aVertexRange) {
2064 // If there's a vertex range, then we need to draw triangles within from
2065 // generated from a path stored in the path vertex buffer.
2066 mWebgl->DrawArrays(LOCAL_GL_TRIANGLES, GLint(aVertexRange->mOffset),
2067 GLsizei(aVertexRange->mLength));
2068 } else {
2069 // Otherwise we're drawing a simple stroked/filled rectangle.
2070 mWebgl->DrawArrays(
2071 aStrokeOptions ? LOCAL_GL_LINE_LOOP : LOCAL_GL_TRIANGLE_FAN, 0, 4);
2074 // Restore the default linear filter if overridden.
2075 if (UseNearestFilter(surfacePattern)) {
2076 SetTexFilter(tex, true);
2079 success = true;
2080 break;
2082 default:
2083 gfxWarning() << "Unknown DrawTargetWebgl::DrawRect pattern type: "
2084 << (int)aPattern.GetType();
2085 break;
2087 // mWebgl->Disable(LOCAL_GL_BLEND);
2089 // Clean up any scissor state if there was clipping.
2090 if (scissor) {
2091 mWebgl->Disable(LOCAL_GL_SCISSOR_TEST);
2094 return success;
2097 bool DrawTargetWebgl::SharedContext::RemoveSharedTexture(
2098 const RefPtr<SharedTexture>& aTexture) {
2099 auto pos =
2100 std::find(mSharedTextures.begin(), mSharedTextures.end(), aTexture);
2101 if (pos == mSharedTextures.end()) {
2102 return false;
2104 // Keep around a reserve of empty pages to avoid initialization costs from
2105 // allocating shared pages. If still below the limit of reserved pages, then
2106 // just add it to the reserve. Otherwise, erase the empty texture page.
2107 size_t maxBytes = StaticPrefs::gfx_canvas_accelerated_reserve_empty_cache()
2108 << 20;
2109 size_t usedBytes = aTexture->UsedBytes();
2110 if (mEmptyTextureMemory + usedBytes <= maxBytes) {
2111 mEmptyTextureMemory += usedBytes;
2112 } else {
2113 mTotalTextureMemory -= usedBytes;
2114 mSharedTextures.erase(pos);
2115 ClearLastTexture();
2116 mWebgl->DeleteTexture(aTexture->GetWebGLTexture());
2118 return true;
2121 void SharedTextureHandle::Cleanup(DrawTargetWebgl::SharedContext& aContext) {
2122 mTexture->Free(*this);
2124 // Check if the shared handle's owning page has no more allocated handles
2125 // after we freed it. If so, remove the empty shared texture page also.
2126 if (!mTexture->HasAllocatedHandles()) {
2127 aContext.RemoveSharedTexture(mTexture);
2131 bool DrawTargetWebgl::SharedContext::RemoveStandaloneTexture(
2132 const RefPtr<StandaloneTexture>& aTexture) {
2133 auto pos = std::find(mStandaloneTextures.begin(), mStandaloneTextures.end(),
2134 aTexture);
2135 if (pos == mStandaloneTextures.end()) {
2136 return false;
2138 mTotalTextureMemory -= aTexture->UsedBytes();
2139 mStandaloneTextures.erase(pos);
2140 ClearLastTexture();
2141 mWebgl->DeleteTexture(aTexture->GetWebGLTexture());
2142 return true;
2145 void StandaloneTexture::Cleanup(DrawTargetWebgl::SharedContext& aContext) {
2146 aContext.RemoveStandaloneTexture(this);
2149 // Prune a given texture handle and release its associated resources.
2150 void DrawTargetWebgl::SharedContext::PruneTextureHandle(
2151 const RefPtr<TextureHandle>& aHandle) {
2152 // Invalidate the handle so nothing will subsequently use its contents.
2153 aHandle->Invalidate();
2154 // If the handle has an associated SourceSurface, unlink it.
2155 UnlinkSurfaceTexture(aHandle);
2156 // If the handle has an associated CacheEntry, unlink it.
2157 if (RefPtr<CacheEntry> entry = aHandle->GetCacheEntry()) {
2158 entry->Unlink();
2160 // Deduct the used space from the total.
2161 mUsedTextureMemory -= aHandle->UsedBytes();
2162 // Ensure any allocated shared or standalone texture regions get freed.
2163 aHandle->Cleanup(*this);
2166 // Prune any texture memory above the limit (or margin below the limit) or any
2167 // least-recently-used handles that are no longer associated with any usable
2168 // surface.
2169 bool DrawTargetWebgl::SharedContext::PruneTextureMemory(size_t aMargin,
2170 bool aPruneUnused) {
2171 // The maximum amount of texture memory that may be used by textures.
2172 size_t maxBytes = StaticPrefs::gfx_canvas_accelerated_cache_size() << 20;
2173 maxBytes -= std::min(maxBytes, aMargin);
2174 size_t maxItems = StaticPrefs::gfx_canvas_accelerated_cache_items();
2175 size_t oldItems = mNumTextureHandles;
2176 while (!mTextureHandles.isEmpty() &&
2177 (mUsedTextureMemory > maxBytes || mNumTextureHandles > maxItems ||
2178 (aPruneUnused && !mTextureHandles.getLast()->IsUsed()))) {
2179 PruneTextureHandle(mTextureHandles.popLast());
2180 --mNumTextureHandles;
2182 return mNumTextureHandles < oldItems;
2185 void DrawTargetWebgl::FillRect(const Rect& aRect, const Pattern& aPattern,
2186 const DrawOptions& aOptions) {
2187 if (SupportsPattern(aPattern)) {
2188 DrawRect(aRect, aPattern, aOptions);
2189 } else if (!mWebglValid) {
2190 MarkSkiaChanged(aOptions);
2191 mSkia->FillRect(aRect, aPattern, aOptions);
2192 } else {
2193 // If the pattern is unsupported, then transform the rect to a path so it
2194 // can be cached.
2195 SkPath skiaPath;
2196 skiaPath.addRect(RectToSkRect(aRect));
2197 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
2198 DrawPath(path, aPattern, aOptions);
2202 void CacheEntry::Link(const RefPtr<TextureHandle>& aHandle) {
2203 mHandle = aHandle;
2204 mHandle->SetCacheEntry(this);
2207 // When the CacheEntry becomes unused, it marks the corresponding
2208 // TextureHandle as unused and unlinks it from the CacheEntry. The
2209 // entry is removed from its containing Cache, if applicable.
2210 void CacheEntry::Unlink() {
2211 // The entry may not have a valid handle if rasterization failed.
2212 if (mHandle) {
2213 mHandle->SetCacheEntry(nullptr);
2214 mHandle = nullptr;
2217 RemoveFromList();
2220 // Hashes a path and pattern to a single hash value that can be used for quick
2221 // comparisons. This currently avoids to expensive hashing of internal path
2222 // and pattern data for speed, relying instead on later exact comparisons for
2223 // disambiguation.
2224 HashNumber PathCacheEntry::HashPath(const QuantizedPath& aPath,
2225 const Pattern* aPattern,
2226 const Matrix& aTransform,
2227 const IntRect& aBounds,
2228 const Point& aOrigin) {
2229 HashNumber hash = 0;
2230 hash = AddToHash(hash, aPath.mPath.num_types);
2231 hash = AddToHash(hash, aPath.mPath.num_points);
2232 // Quantize the relative offset of the path to its bounds.
2233 IntPoint offset = RoundedToInt((aOrigin - Point(aBounds.TopLeft())) * 16.0f);
2234 hash = AddToHash(hash, offset.x);
2235 hash = AddToHash(hash, offset.y);
2236 hash = AddToHash(hash, aBounds.width);
2237 hash = AddToHash(hash, aBounds.height);
2238 if (aPattern) {
2239 hash = AddToHash(hash, (int)aPattern->GetType());
2241 return hash;
2244 // When caching rendered geometry, we need to ensure the scale and orientation
2245 // is approximately the same. The offset will be considered separately.
2246 static inline bool HasMatchingScale(const Matrix& aTransform1,
2247 const Matrix& aTransform2) {
2248 return FuzzyEqual(aTransform1._11, aTransform2._11) &&
2249 FuzzyEqual(aTransform1._12, aTransform2._12) &&
2250 FuzzyEqual(aTransform1._21, aTransform2._21) &&
2251 FuzzyEqual(aTransform1._22, aTransform2._22);
2254 // Determines if an existing path cache entry matches an incoming path and
2255 // pattern.
2256 inline bool PathCacheEntry::MatchesPath(const QuantizedPath& aPath,
2257 const Pattern* aPattern,
2258 const StrokeOptions* aStrokeOptions,
2259 const Matrix& aTransform,
2260 const IntRect& aBounds,
2261 const Point& aOrigin, HashNumber aHash,
2262 float aSigma) {
2263 return aHash == mHash && HasMatchingScale(aTransform, mTransform) &&
2264 // Ensure the clipped relative bounds fit inside those of the entry
2265 aBounds.x - aOrigin.x >= mBounds.x - mOrigin.x &&
2266 (aBounds.x - aOrigin.x) + aBounds.width <=
2267 (mBounds.x - mOrigin.x) + mBounds.width &&
2268 aBounds.y - aOrigin.y >= mBounds.y - mOrigin.y &&
2269 (aBounds.y - aOrigin.y) + aBounds.height <=
2270 (mBounds.y - mOrigin.y) + mBounds.height &&
2271 aPath == mPath &&
2272 (!aPattern ? !mPattern : mPattern && *aPattern == *mPattern) &&
2273 (!aStrokeOptions
2274 ? !mStrokeOptions
2275 : mStrokeOptions && *aStrokeOptions == *mStrokeOptions) &&
2276 aSigma == mSigma;
2279 PathCacheEntry::PathCacheEntry(QuantizedPath&& aPath, Pattern* aPattern,
2280 StoredStrokeOptions* aStrokeOptions,
2281 const Matrix& aTransform, const IntRect& aBounds,
2282 const Point& aOrigin, HashNumber aHash,
2283 float aSigma)
2284 : CacheEntryImpl<PathCacheEntry>(aTransform, aBounds, aHash),
2285 mPath(std::move(aPath)),
2286 mOrigin(aOrigin),
2287 mPattern(aPattern),
2288 mStrokeOptions(aStrokeOptions),
2289 mSigma(aSigma) {}
2291 // Attempt to find a matching entry in the path cache. If one isn't found,
2292 // a new entry will be created. The caller should check whether the contained
2293 // texture handle is valid to determine if it will need to render the text run
2294 // or just reuse the cached texture.
2295 already_AddRefed<PathCacheEntry> PathCache::FindOrInsertEntry(
2296 QuantizedPath aPath, const Pattern* aPattern,
2297 const StrokeOptions* aStrokeOptions, const Matrix& aTransform,
2298 const IntRect& aBounds, const Point& aOrigin, float aSigma) {
2299 HashNumber hash =
2300 PathCacheEntry::HashPath(aPath, aPattern, aTransform, aBounds, aOrigin);
2301 for (const RefPtr<PathCacheEntry>& entry : GetChain(hash)) {
2302 if (entry->MatchesPath(aPath, aPattern, aStrokeOptions, aTransform, aBounds,
2303 aOrigin, hash, aSigma)) {
2304 return do_AddRef(entry);
2307 Pattern* pattern = nullptr;
2308 if (aPattern) {
2309 pattern = aPattern->CloneWeak();
2310 if (!pattern) {
2311 return nullptr;
2314 StoredStrokeOptions* strokeOptions = nullptr;
2315 if (aStrokeOptions) {
2316 strokeOptions = aStrokeOptions->Clone();
2317 if (!strokeOptions) {
2318 return nullptr;
2321 RefPtr<PathCacheEntry> entry =
2322 new PathCacheEntry(std::move(aPath), pattern, strokeOptions, aTransform,
2323 aBounds, aOrigin, hash, aSigma);
2324 Insert(entry);
2325 return entry.forget();
2328 void DrawTargetWebgl::Fill(const Path* aPath, const Pattern& aPattern,
2329 const DrawOptions& aOptions) {
2330 if (!aPath || aPath->GetBackendType() != BackendType::SKIA) {
2331 return;
2333 const SkPath& skiaPath = static_cast<const PathSkia*>(aPath)->GetPath();
2334 SkRect rect;
2335 // Draw the path as a simple rectangle with a supported pattern when possible.
2336 if (skiaPath.isRect(&rect) && SupportsPattern(aPattern)) {
2337 DrawRect(SkRectToRect(rect), aPattern, aOptions);
2338 } else {
2339 DrawPath(aPath, aPattern, aOptions);
2343 QuantizedPath::QuantizedPath(const WGR::Path& aPath) : mPath(aPath) {}
2345 QuantizedPath::QuantizedPath(QuantizedPath&& aPath) noexcept
2346 : mPath(aPath.mPath) {
2347 aPath.mPath.points = nullptr;
2348 aPath.mPath.num_points = 0;
2349 aPath.mPath.types = nullptr;
2350 aPath.mPath.num_types = 0;
2353 QuantizedPath::~QuantizedPath() {
2354 if (mPath.points || mPath.types) {
2355 WGR::wgr_path_release(mPath);
2359 bool QuantizedPath::operator==(const QuantizedPath& aOther) const {
2360 return mPath.num_types == aOther.mPath.num_types &&
2361 mPath.num_points == aOther.mPath.num_points &&
2362 mPath.fill_mode == aOther.mPath.fill_mode &&
2363 !memcmp(mPath.types, aOther.mPath.types,
2364 mPath.num_types * sizeof(uint8_t)) &&
2365 !memcmp(mPath.points, aOther.mPath.points,
2366 mPath.num_points * sizeof(WGR::Point));
2369 // Generate a quantized path from the Skia path using WGR. The supplied
2370 // transform will be applied to the path. The path is stored relative to its
2371 // bounds origin to support translation later.
2372 static Maybe<QuantizedPath> GenerateQuantizedPath(const SkPath& aPath,
2373 const IntRect& aBounds,
2374 const Matrix& aTransform) {
2375 WGR::PathBuilder* pb = WGR::wgr_new_builder();
2376 if (!pb) {
2377 return Nothing();
2379 WGR::wgr_builder_set_fill_mode(
2380 pb, aPath.getFillType() == SkPath::kWinding_FillType
2381 ? WGR::FillMode::Winding
2382 : WGR::FillMode::EvenOdd);
2384 SkPath::RawIter iter(aPath);
2385 SkPoint params[4];
2386 SkPath::Verb currentVerb;
2388 // printf_stderr("bounds: (%d, %d) %d x %d\n", aBounds.x, aBounds.y,
2389 // aBounds.width, aBounds.height);
2390 Matrix transform = aTransform;
2391 transform.PostTranslate(-aBounds.TopLeft());
2392 while ((currentVerb = iter.next(params)) != SkPath::kDone_Verb) {
2393 switch (currentVerb) {
2394 case SkPath::kMove_Verb: {
2395 Point p0 = transform.TransformPoint(SkPointToPoint(params[0]));
2396 // printf_stderr("move (%f, %f)\n", p0.x, p0.y);
2397 WGR::wgr_builder_move_to(pb, p0.x, p0.y);
2398 break;
2400 case SkPath::kLine_Verb: {
2401 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2402 // printf_stderr("line (%f, %f)\n", p1.x, p1.y);
2403 WGR::wgr_builder_line_to(pb, p1.x, p1.y);
2404 break;
2406 case SkPath::kCubic_Verb: {
2407 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2408 Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
2409 Point p3 = transform.TransformPoint(SkPointToPoint(params[3]));
2410 // printf_stderr("cubic (%f, %f), (%f, %f), (%f, %f)\n", p1.x, p1.y,
2411 // p2.x, p2.y, p3.x, p3.y);
2412 WGR::wgr_builder_curve_to(pb, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
2413 break;
2415 case SkPath::kQuad_Verb: {
2416 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2417 Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
2418 // printf_stderr("quad (%f, %f), (%f, %f)\n", p1.x, p1.y, p2.x, p2.y);
2419 WGR::wgr_builder_quad_to(pb, p1.x, p1.y, p2.x, p2.y);
2420 break;
2422 case SkPath::kConic_Verb: {
2423 Point p0 = transform.TransformPoint(SkPointToPoint(params[0]));
2424 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2425 Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
2426 float w = iter.conicWeight();
2427 std::vector<Point> quads;
2428 int numQuads = ConvertConicToQuads(p0, p1, p2, w, quads);
2429 for (int i = 0; i < numQuads; i++) {
2430 Point q1 = quads[2 * i + 1];
2431 Point q2 = quads[2 * i + 2];
2432 // printf_stderr("conic quad (%f, %f), (%f, %f)\n", q1.x, q1.y, q2.x,
2433 // q2.y);
2434 WGR::wgr_builder_quad_to(pb, q1.x, q1.y, q2.x, q2.y);
2436 break;
2438 case SkPath::kClose_Verb:
2439 // printf_stderr("close\n");
2440 WGR::wgr_builder_close(pb);
2441 break;
2442 default:
2443 MOZ_ASSERT(false);
2444 // Unexpected verb found in path!
2445 WGR::wgr_builder_release(pb);
2446 return Nothing();
2450 WGR::Path p = WGR::wgr_builder_get_path(pb);
2451 WGR::wgr_builder_release(pb);
2452 if (!p.num_points || !p.num_types) {
2453 WGR::wgr_path_release(p);
2454 return Nothing();
2456 return Some(QuantizedPath(p));
2459 // Get the output vertex buffer using WGR from an input quantized path.
2460 static Maybe<WGR::VertexBuffer> GeneratePathVertexBuffer(
2461 const QuantizedPath& aPath, const IntRect& aClipRect) {
2462 WGR::VertexBuffer vb = WGR::wgr_path_rasterize_to_tri_list(
2463 &aPath.mPath, aClipRect.x, aClipRect.y, aClipRect.width,
2464 aClipRect.height);
2465 if (!vb.len) {
2466 WGR::wgr_vertex_buffer_release(vb);
2467 return Nothing();
2469 return Some(vb);
2472 // Search the path cache for any entries stored in the path vertex buffer and
2473 // remove them.
2474 void PathCache::ClearVertexRanges() {
2475 for (auto& chain : mChains) {
2476 PathCacheEntry* entry = chain.getFirst();
2477 while (entry) {
2478 PathCacheEntry* next = entry->getNext();
2479 if (entry->GetVertexRange().IsValid()) {
2480 entry->Unlink();
2482 entry = next;
2487 inline bool DrawTargetWebgl::ShouldAccelPath(const DrawOptions& aOptions) {
2488 return mWebglValid && SupportsDrawOptions(aOptions) && PrepareContext();
2491 bool DrawTargetWebgl::SharedContext::DrawPathAccel(
2492 const Path* aPath, const Pattern& aPattern, const DrawOptions& aOptions,
2493 const StrokeOptions* aStrokeOptions, const ShadowOptions* aShadow,
2494 bool aCacheable) {
2495 // Get the transformed bounds for the path and conservatively check if the
2496 // bounds overlap the canvas.
2497 const PathSkia* pathSkia = static_cast<const PathSkia*>(aPath);
2498 const Matrix& currentTransform = GetTransform();
2499 Rect bounds = pathSkia->GetFastBounds(currentTransform, aStrokeOptions);
2500 // If the path is empty, then there is nothing to draw.
2501 if (bounds.IsEmpty()) {
2502 return true;
2504 IntRect viewport(IntPoint(), mViewportSize);
2505 if (aShadow) {
2506 // Inflate the bounds to account for the blur radius.
2507 bounds += aShadow->mOffset;
2508 int32_t blurRadius = aShadow->BlurRadius();
2509 bounds.Inflate(blurRadius);
2510 viewport.Inflate(blurRadius);
2512 // If the path doesn't intersect the viewport, then there is nothing to draw.
2513 IntRect intBounds = RoundedOut(bounds).Intersect(viewport);
2514 if (intBounds.IsEmpty()) {
2515 return true;
2517 // If a stroke path covers too much screen area, it is likely that most is
2518 // empty space in the interior. This usually imposes too high a cost versus
2519 // just rasterizing without acceleration.
2520 if (aStrokeOptions &&
2521 intBounds.width * intBounds.height >
2522 (mViewportSize.width / 2) * (mViewportSize.height / 2)) {
2523 return false;
2525 // If the pattern is a solid color, then this will be used along with a path
2526 // mask to render the path, as opposed to baking the pattern into the cached
2527 // path texture.
2528 Maybe<DeviceColor> color =
2529 aPattern.GetType() == PatternType::COLOR
2530 ? Some(static_cast<const ColorPattern&>(aPattern).mColor)
2531 : Nothing();
2532 // Look for an existing path cache entry, if possible, or otherwise create
2533 // one. If the draw request is not cacheable, then don't create an entry.
2534 RefPtr<PathCacheEntry> entry;
2535 RefPtr<TextureHandle> handle;
2536 if (aCacheable) {
2537 if (!mPathCache) {
2538 mPathCache = MakeUnique<PathCache>();
2540 // Use a quantized, relative (to its bounds origin) version of the path as
2541 // a cache key to help limit cache bloat.
2542 Maybe<QuantizedPath> qp =
2543 GenerateQuantizedPath(pathSkia->GetPath(), intBounds, currentTransform);
2544 if (!qp) {
2545 return false;
2547 entry = mPathCache->FindOrInsertEntry(
2548 std::move(*qp), color ? nullptr : &aPattern, aStrokeOptions,
2549 currentTransform, intBounds, bounds.TopLeft(),
2550 aShadow ? aShadow->mSigma : -1.0f);
2551 if (!entry) {
2552 return false;
2554 handle = entry->GetHandle();
2557 // If there is a shadow, it needs to draw with the shadow color rather than
2558 // the path color.
2559 Maybe<DeviceColor> shadowColor = color;
2560 if (aShadow) {
2561 shadowColor = Some(aShadow->mColor);
2562 if (color) {
2563 shadowColor->a *= color->a;
2566 SamplingFilter filter =
2567 aShadow ? SamplingFilter::GOOD : GetSamplingFilter(aPattern);
2568 if (handle && handle->IsValid()) {
2569 // If the entry has a valid texture handle still, use it. However, the
2570 // entry texture is assumed to be located relative to its previous bounds.
2571 // We need to offset the pattern by the difference between its new unclipped
2572 // origin and its previous previous unclipped origin. Then when we finally
2573 // draw a rectangle at the expected new bounds, it will overlap the portion
2574 // of the old entry texture we actually need to sample from.
2575 Point offset =
2576 (bounds.TopLeft() - entry->GetOrigin()) + entry->GetBounds().TopLeft();
2577 SurfacePattern pathPattern(nullptr, ExtendMode::CLAMP,
2578 Matrix::Translation(offset), filter);
2579 return DrawRectAccel(Rect(intBounds), pathPattern, aOptions, shadowColor,
2580 &handle, false, true, true);
2583 if (mPathVertexCapacity > 0 && !handle && entry && !aShadow &&
2584 SupportsPattern(aPattern)) {
2585 if (entry->GetVertexRange().IsValid()) {
2586 // If there is a valid cached vertex data in the path vertex buffer, then
2587 // just draw that.
2588 mCurrentTarget->mProfile.OnCacheHit();
2589 return DrawRectAccel(Rect(intBounds.TopLeft(), Size(1, 1)), aPattern,
2590 aOptions, Nothing(), nullptr, false, true, true,
2591 false, nullptr, &entry->GetVertexRange());
2594 // printf_stderr("Generating... verbs %d, points %d\n",
2595 // int(pathSkia->GetPath().countVerbs()),
2596 // int(pathSkia->GetPath().countPoints()));
2597 Maybe<WGR::VertexBuffer> vb;
2598 if (aStrokeOptions) {
2599 // If stroking, then generate a path to fill the stroked region. This
2600 // path will need to be quantized again because it differs from the path
2601 // used for the cache entry, but this allows us to avoid generating a
2602 // fill path on a cache hit.
2603 SkPaint paint;
2604 if (StrokeOptionsToPaint(paint, *aStrokeOptions)) {
2605 Maybe<SkRect> cullRect;
2606 Matrix invTransform = currentTransform;
2607 if (invTransform.Invert()) {
2608 // Transform the stroking clip rect from device space to local space.
2609 Rect invRect = invTransform.TransformBounds(Rect(mClipRect));
2610 invRect.RoundOut();
2611 cullRect = Some(RectToSkRect(invRect));
2613 SkPath fillPath;
2614 if (paint.getFillPath(pathSkia->GetPath(), &fillPath,
2615 cullRect.ptrOr(nullptr),
2616 ComputeResScaleForStroking(currentTransform))) {
2617 // printf_stderr(" stroke fill... verbs %d, points %d\n",
2618 // int(fillPath.countVerbs()),
2619 // int(fillPath.countPoints()));
2620 if (Maybe<QuantizedPath> qp = GenerateQuantizedPath(
2621 fillPath, intBounds, currentTransform)) {
2622 vb = GeneratePathVertexBuffer(
2623 *qp, IntRect(-intBounds.TopLeft(), mViewportSize));
2627 } else {
2628 vb = GeneratePathVertexBuffer(
2629 entry->GetPath(), IntRect(-intBounds.TopLeft(), mViewportSize));
2631 if (vb) {
2632 uint32_t vertexBytes = vb->len * sizeof(WGR::OutputVertex);
2633 // printf_stderr(" ... %d verts, %d bytes\n", int(vb->len),
2634 // int(vertexBytes));
2635 if (vertexBytes > mPathVertexCapacity - mPathVertexOffset &&
2636 vertexBytes <= mPathVertexCapacity - sizeof(kRectVertexData)) {
2637 // If the vertex data is too large to fit in the remaining path vertex
2638 // buffer, then orphan the contents of the vertex buffer to make room
2639 // for it.
2640 if (mPathCache) {
2641 mPathCache->ClearVertexRanges();
2643 ResetPathVertexBuffer();
2645 if (vertexBytes <= mPathVertexCapacity - mPathVertexOffset) {
2646 // If there is actually room to fit the vertex data in the vertex buffer
2647 // after orphaning as necessary, then upload the data to the next
2648 // available offset in the buffer.
2649 PathVertexRange vertexRange(
2650 uint32_t(mPathVertexOffset / sizeof(WGR::OutputVertex)),
2651 uint32_t(vb->len));
2652 if (entry) {
2653 entry->SetVertexRange(vertexRange);
2655 // printf_stderr(" ... offset %d\n", mPathVertexOffset);
2656 mWebgl->RawBufferSubData(LOCAL_GL_ARRAY_BUFFER, mPathVertexOffset,
2657 (const uint8_t*)vb->data, vertexBytes);
2658 mPathVertexOffset += vertexBytes;
2659 wgr_vertex_buffer_release(vb.ref());
2660 // Finally, draw the uploaded vertex data.
2661 mCurrentTarget->mProfile.OnCacheMiss();
2662 return DrawRectAccel(Rect(intBounds.TopLeft(), Size(1, 1)), aPattern,
2663 aOptions, Nothing(), nullptr, false, true, true,
2664 false, nullptr, &vertexRange);
2666 wgr_vertex_buffer_release(vb.ref());
2667 // If we failed to draw the vertex data for some reason, then fall through
2668 // to the texture rasterization path.
2672 // If there isn't a valid texture handle, then we need to rasterize the
2673 // path in a software canvas and upload this to a texture. Solid color
2674 // patterns will be rendered as a path mask that can then be modulated
2675 // with any color. Other pattern types have to rasterize the pattern
2676 // directly into the cached texture.
2677 handle = nullptr;
2678 RefPtr<DrawTargetSkia> pathDT = new DrawTargetSkia;
2679 if (pathDT->Init(intBounds.Size(), color || aShadow
2680 ? SurfaceFormat::A8
2681 : SurfaceFormat::B8G8R8A8)) {
2682 Point offset = -intBounds.TopLeft();
2683 if (aShadow) {
2684 // Ensure the the shadow is drawn at the requested offset
2685 offset += aShadow->mOffset;
2687 pathDT->SetTransform(currentTransform * Matrix::Translation(offset));
2688 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER,
2689 aOptions.mAntialiasMode);
2690 static const ColorPattern maskPattern(DeviceColor(1.0f, 1.0f, 1.0f, 1.0f));
2691 const Pattern& cachePattern = color ? maskPattern : aPattern;
2692 // If the source pattern is a DrawTargetWebgl snapshot, we may shift
2693 // targets when drawing the path, so back up the old target.
2694 DrawTargetWebgl* oldTarget = mCurrentTarget;
2695 if (aStrokeOptions) {
2696 pathDT->Stroke(aPath, cachePattern, *aStrokeOptions, drawOptions);
2697 } else {
2698 pathDT->Fill(aPath, cachePattern, drawOptions);
2700 if (aShadow && aShadow->mSigma > 0.0f) {
2701 // Blur the shadow if required.
2702 uint8_t* data = nullptr;
2703 IntSize size;
2704 int32_t stride = 0;
2705 SurfaceFormat format = SurfaceFormat::UNKNOWN;
2706 if (pathDT->LockBits(&data, &size, &stride, &format)) {
2707 AlphaBoxBlur blur(Rect(pathDT->GetRect()), stride, aShadow->mSigma,
2708 aShadow->mSigma);
2709 blur.Blur(data);
2710 pathDT->ReleaseBits(data);
2713 RefPtr<SourceSurface> pathSurface = pathDT->Snapshot();
2714 if (pathSurface) {
2715 // If the target changed, try to restore it.
2716 if (mCurrentTarget != oldTarget && !oldTarget->PrepareContext()) {
2717 return false;
2719 SurfacePattern pathPattern(pathSurface, ExtendMode::CLAMP,
2720 Matrix::Translation(intBounds.TopLeft()),
2721 filter);
2722 // Try and upload the rasterized path to a texture. If there is a
2723 // valid texture handle after this, then link it to the entry.
2724 // Otherwise, we might have to fall back to software drawing the
2725 // path, so unlink it from the entry.
2726 if (DrawRectAccel(Rect(intBounds), pathPattern, aOptions, shadowColor,
2727 &handle, false, true) &&
2728 handle) {
2729 if (entry) {
2730 entry->Link(handle);
2732 } else if (entry) {
2733 entry->Unlink();
2735 return true;
2739 return false;
2742 void DrawTargetWebgl::DrawPath(const Path* aPath, const Pattern& aPattern,
2743 const DrawOptions& aOptions,
2744 const StrokeOptions* aStrokeOptions) {
2745 // If there is a WebGL context, then try to cache the path to avoid slow
2746 // fallbacks.
2747 if (ShouldAccelPath(aOptions) &&
2748 mSharedContext->DrawPathAccel(aPath, aPattern, aOptions,
2749 aStrokeOptions)) {
2750 return;
2753 // There was no path cache entry available to use, so fall back to drawing the
2754 // path with Skia.
2755 MarkSkiaChanged(aOptions);
2756 if (aStrokeOptions) {
2757 mSkia->Stroke(aPath, aPattern, *aStrokeOptions, aOptions);
2758 } else {
2759 mSkia->Fill(aPath, aPattern, aOptions);
2763 void DrawTargetWebgl::DrawSurface(SourceSurface* aSurface, const Rect& aDest,
2764 const Rect& aSource,
2765 const DrawSurfaceOptions& aSurfOptions,
2766 const DrawOptions& aOptions) {
2767 Matrix matrix = Matrix::Scaling(aDest.width / aSource.width,
2768 aDest.height / aSource.height);
2769 matrix.PreTranslate(-aSource.x, -aSource.y);
2770 matrix.PostTranslate(aDest.x, aDest.y);
2771 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, matrix,
2772 aSurfOptions.mSamplingFilter);
2773 DrawRect(aDest, pattern, aOptions);
2776 void DrawTargetWebgl::Mask(const Pattern& aSource, const Pattern& aMask,
2777 const DrawOptions& aOptions) {
2778 if (!SupportsDrawOptions(aOptions) ||
2779 aMask.GetType() != PatternType::SURFACE ||
2780 aSource.GetType() != PatternType::COLOR) {
2781 MarkSkiaChanged(aOptions);
2782 mSkia->Mask(aSource, aMask, aOptions);
2783 return;
2785 auto sourceColor = static_cast<const ColorPattern&>(aSource).mColor;
2786 auto maskPattern = static_cast<const SurfacePattern&>(aMask);
2787 DrawRect(Rect(IntRect(IntPoint(), maskPattern.mSurface->GetSize())),
2788 maskPattern, aOptions, Some(sourceColor));
2791 void DrawTargetWebgl::MaskSurface(const Pattern& aSource, SourceSurface* aMask,
2792 Point aOffset, const DrawOptions& aOptions) {
2793 if (!SupportsDrawOptions(aOptions) ||
2794 aSource.GetType() != PatternType::COLOR) {
2795 MarkSkiaChanged(aOptions);
2796 mSkia->MaskSurface(aSource, aMask, aOffset, aOptions);
2797 } else {
2798 auto sourceColor = static_cast<const ColorPattern&>(aSource).mColor;
2799 SurfacePattern pattern(aMask, ExtendMode::CLAMP,
2800 Matrix::Translation(aOffset));
2801 DrawRect(Rect(aOffset, Size(aMask->GetSize())), pattern, aOptions,
2802 Some(sourceColor));
2806 // Extract the surface's alpha values into an A8 surface.
2807 static already_AddRefed<DataSourceSurface> ExtractAlpha(SourceSurface* aSurface,
2808 bool aAllowSubpixelAA) {
2809 RefPtr<DataSourceSurface> surfaceData = aSurface->GetDataSurface();
2810 if (!surfaceData) {
2811 return nullptr;
2813 DataSourceSurface::ScopedMap srcMap(surfaceData, DataSourceSurface::READ);
2814 if (!srcMap.IsMapped()) {
2815 return nullptr;
2817 IntSize size = surfaceData->GetSize();
2818 RefPtr<DataSourceSurface> alpha =
2819 Factory::CreateDataSourceSurface(size, SurfaceFormat::A8, false);
2820 if (!alpha) {
2821 return nullptr;
2823 DataSourceSurface::ScopedMap dstMap(alpha, DataSourceSurface::WRITE);
2824 if (!dstMap.IsMapped()) {
2825 return nullptr;
2827 // For subpixel masks, ignore the alpha and instead sample one of the color
2828 // channels as if they were alpha.
2829 SwizzleData(
2830 srcMap.GetData(), srcMap.GetStride(),
2831 aAllowSubpixelAA ? SurfaceFormat::A8R8G8B8 : surfaceData->GetFormat(),
2832 dstMap.GetData(), dstMap.GetStride(), SurfaceFormat::A8, size);
2833 return alpha.forget();
2836 void DrawTargetWebgl::DrawShadow(const Path* aPath, const Pattern& aPattern,
2837 const ShadowOptions& aShadow,
2838 const DrawOptions& aOptions,
2839 const StrokeOptions* aStrokeOptions) {
2840 // If there is a WebGL context, then try to cache the path to avoid slow
2841 // fallbacks.
2842 if (ShouldAccelPath(aOptions) &&
2843 mSharedContext->DrawPathAccel(aPath, aPattern, aOptions, aStrokeOptions,
2844 &aShadow)) {
2845 return;
2848 // There was no path cache entry available to use, so fall back to drawing the
2849 // path with Skia.
2850 MarkSkiaChanged(aOptions);
2851 mSkia->DrawShadow(aPath, aPattern, aShadow, aOptions, aStrokeOptions);
2854 void DrawTargetWebgl::DrawSurfaceWithShadow(SourceSurface* aSurface,
2855 const Point& aDest,
2856 const ShadowOptions& aShadow,
2857 CompositionOp aOperator) {
2858 DrawOptions options(1.0f, aOperator);
2859 if (ShouldAccelPath(options)) {
2860 SurfacePattern pattern(aSurface, ExtendMode::CLAMP,
2861 Matrix::Translation(aDest));
2862 SkPath skiaPath;
2863 skiaPath.addRect(RectToSkRect(Rect(aSurface->GetRect()) + aDest));
2864 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
2865 AutoRestoreTransform restore(this);
2866 SetTransform(Matrix());
2867 if (mSharedContext->DrawPathAccel(path, pattern, options, nullptr, &aShadow,
2868 false)) {
2869 DrawRect(Rect(aSurface->GetRect()) + aDest, pattern, options);
2870 return;
2874 MarkSkiaChanged(options);
2875 mSkia->DrawSurfaceWithShadow(aSurface, aDest, aShadow, aOperator);
2878 already_AddRefed<PathBuilder> DrawTargetWebgl::CreatePathBuilder(
2879 FillRule aFillRule) const {
2880 return mSkia->CreatePathBuilder(aFillRule);
2883 void DrawTargetWebgl::SetTransform(const Matrix& aTransform) {
2884 DrawTarget::SetTransform(aTransform);
2885 mSkia->SetTransform(aTransform);
2888 bool DrawTargetWebgl::StrokeRectAccel(const Rect& aRect,
2889 const Pattern& aPattern,
2890 const StrokeOptions& aStrokeOptions,
2891 const DrawOptions& aOptions) {
2892 // TODO: Support other stroke options. Ensure that we only stroke with the
2893 // default settings for now.
2894 if (mWebglValid && SupportsPattern(aPattern) &&
2895 aStrokeOptions == StrokeOptions() && mTransform.PreservesDistance()) {
2896 DrawRect(aRect, aPattern, aOptions, Nothing(), nullptr, true, true, false,
2897 false, &aStrokeOptions);
2898 return true;
2900 return false;
2903 void DrawTargetWebgl::StrokeRect(const Rect& aRect, const Pattern& aPattern,
2904 const StrokeOptions& aStrokeOptions,
2905 const DrawOptions& aOptions) {
2906 if (!mWebglValid) {
2907 MarkSkiaChanged(aOptions);
2908 mSkia->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions);
2909 } else if (!StrokeRectAccel(aRect, aPattern, aStrokeOptions, aOptions)) {
2910 // If the stroke options are unsupported, then transform the rect to a path
2911 // so it can be cached.
2912 SkPath skiaPath;
2913 skiaPath.addRect(RectToSkRect(aRect));
2914 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
2915 DrawPath(path, aPattern, aOptions, &aStrokeOptions);
2919 bool DrawTargetWebgl::StrokeLineAccel(const Point& aStart, const Point& aEnd,
2920 const Pattern& aPattern,
2921 const StrokeOptions& aStrokeOptions,
2922 const DrawOptions& aOptions) {
2923 if (mWebglValid && SupportsPattern(aPattern) &&
2924 (aStrokeOptions.mLineCap == CapStyle::BUTT ||
2925 aStrokeOptions.mLineCap == CapStyle::SQUARE) &&
2926 aStrokeOptions.mDashPattern == nullptr && aStrokeOptions.mLineWidth > 0) {
2927 // Treat the line as a rectangle whose center-line is the supplied line and
2928 // for which the height is the supplied line width. Generate a matrix that
2929 // maps the X axis to the orientation of the line and the Y axis to the
2930 // normal vector to the line. This only works if the line caps are squared,
2931 // as rounded rectangles are currently not supported for round line caps.
2932 Point start = aStart;
2933 Point dirX = aEnd - aStart;
2934 float scale = aStrokeOptions.mLineWidth / dirX.Length();
2935 Point dirY = Point(-dirX.y, dirX.x) * scale;
2936 if (aStrokeOptions.mLineCap == CapStyle::SQUARE) {
2937 start -= (dirX * scale) * 0.5f;
2938 dirX += dirX * scale;
2940 Matrix lineXform(dirX.x, dirX.y, dirY.x, dirY.y, start.x - 0.5f * dirY.x,
2941 start.y - 0.5f * dirY.y);
2942 AutoRestoreTransform restore(this);
2943 ConcatTransform(lineXform);
2944 if (DrawRect(Rect(0, 0, 1, 1), aPattern, aOptions, Nothing(), nullptr, true,
2945 true, true)) {
2946 return true;
2949 return false;
2952 void DrawTargetWebgl::StrokeLine(const Point& aStart, const Point& aEnd,
2953 const Pattern& aPattern,
2954 const StrokeOptions& aStrokeOptions,
2955 const DrawOptions& aOptions) {
2956 if (!mWebglValid) {
2957 MarkSkiaChanged(aOptions);
2958 mSkia->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions);
2959 } else if (!StrokeLineAccel(aStart, aEnd, aPattern, aStrokeOptions,
2960 aOptions)) {
2961 // If the stroke options are unsupported, then transform the line to a path
2962 // so it can be cached.
2963 SkPath skiaPath;
2964 skiaPath.moveTo(PointToSkPoint(aStart));
2965 skiaPath.lineTo(PointToSkPoint(aEnd));
2966 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
2967 DrawPath(path, aPattern, aOptions, &aStrokeOptions);
2971 void DrawTargetWebgl::Stroke(const Path* aPath, const Pattern& aPattern,
2972 const StrokeOptions& aStrokeOptions,
2973 const DrawOptions& aOptions) {
2974 if (!aPath || aPath->GetBackendType() != BackendType::SKIA) {
2975 return;
2977 const auto& skiaPath = static_cast<const PathSkia*>(aPath)->GetPath();
2978 SkRect rect;
2979 if (!mWebglValid) {
2980 MarkSkiaChanged(aOptions);
2981 mSkia->Stroke(aPath, aPattern, aStrokeOptions, aOptions);
2982 return;
2984 if (skiaPath.isRect(&rect)) {
2985 if (StrokeRectAccel(SkRectToRect(rect), aPattern, aStrokeOptions,
2986 aOptions)) {
2987 return;
2989 // If accelerated rect drawing failed, just treat it as a path.
2990 } else {
2991 // Avoid using Skia's isLine here because some paths erroneously include a
2992 // closePath at the end, causing isLine to not detect the line. In that case
2993 // we just draw a line in reverse right over the original line.
2994 int numVerbs = skiaPath.countVerbs();
2995 if (numVerbs >= 2 && numVerbs <= 3) {
2996 uint8_t verbs[3];
2997 skiaPath.getVerbs(verbs, numVerbs);
2998 if (verbs[0] == SkPath::kMove_Verb && verbs[1] == SkPath::kLine_Verb &&
2999 (numVerbs < 3 || verbs[2] == SkPath::kClose_Verb)) {
3000 Point start = SkPointToPoint(skiaPath.getPoint(0));
3001 Point end = SkPointToPoint(skiaPath.getPoint(1));
3002 if (StrokeLineAccel(start, end, aPattern, aStrokeOptions, aOptions)) {
3003 if (numVerbs >= 3) {
3004 StrokeLineAccel(end, start, aPattern, aStrokeOptions, aOptions);
3006 return;
3008 // If accelerated line drawing failed, just treat it as a path.
3012 DrawPath(aPath, aPattern, aOptions, &aStrokeOptions);
3015 bool DrawTargetWebgl::ShouldUseSubpixelAA(ScaledFont* aFont,
3016 const DrawOptions& aOptions) {
3017 AntialiasMode aaMode = aFont->GetDefaultAAMode();
3018 if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
3019 aaMode = aOptions.mAntialiasMode;
3021 return GetPermitSubpixelAA() &&
3022 (aaMode == AntialiasMode::DEFAULT ||
3023 aaMode == AntialiasMode::SUBPIXEL) &&
3024 aOptions.mCompositionOp == CompositionOp::OP_OVER;
3027 void DrawTargetWebgl::StrokeGlyphs(ScaledFont* aFont,
3028 const GlyphBuffer& aBuffer,
3029 const Pattern& aPattern,
3030 const StrokeOptions& aStrokeOptions,
3031 const DrawOptions& aOptions) {
3032 if (!aFont || !aBuffer.mNumGlyphs) {
3033 return;
3036 bool useSubpixelAA = ShouldUseSubpixelAA(aFont, aOptions);
3038 if (mWebglValid && SupportsDrawOptions(aOptions) &&
3039 aPattern.GetType() == PatternType::COLOR && PrepareContext() &&
3040 mSharedContext->DrawGlyphsAccel(aFont, aBuffer, aPattern, aOptions,
3041 &aStrokeOptions, useSubpixelAA)) {
3042 return;
3045 if (useSubpixelAA) {
3046 // Subpixel AA does not support layering because the subpixel masks can't
3047 // blend with the over op.
3048 MarkSkiaChanged();
3049 } else {
3050 MarkSkiaChanged(aOptions);
3052 mSkia->StrokeGlyphs(aFont, aBuffer, aPattern, aStrokeOptions, aOptions);
3055 // Depending on whether we enable subpixel position for a given font, Skia may
3056 // round transformed coordinates differently on each axis. By default, text is
3057 // subpixel quantized horizontally and snapped to a whole integer vertical
3058 // baseline. Axis-flip transforms instead snap to horizontal boundaries while
3059 // subpixel quantizing along the vertical. For other types of transforms, Skia
3060 // just applies subpixel quantization to both axes.
3061 // We must duplicate the amount of quantization Skia applies carefully as a
3062 // boundary value such as 0.49 may round to 0.5 with subpixel quantization,
3063 // but if Skia actually snapped it to a whole integer instead, it would round
3064 // down to 0. If a subsequent glyph with offset 0.51 came in, we might
3065 // mistakenly round it down to 0.5, whereas Skia would round it up to 1. Thus
3066 // we would alias 0.49 and 0.51 to the same cache entry, while Skia would
3067 // actually snap the offset to 0 or 1, depending, resulting in mismatched
3068 // hinting.
3069 static inline IntPoint QuantizeScale(ScaledFont* aFont,
3070 const Matrix& aTransform) {
3071 if (!aFont->UseSubpixelPosition()) {
3072 return {1, 1};
3074 if (aTransform._12 == 0) {
3075 // Glyphs are rendered subpixel horizontally, so snap vertically.
3076 return {4, 1};
3078 if (aTransform._11 == 0) {
3079 // Glyphs are rendered subpixel vertically, so snap horizontally.
3080 return {1, 4};
3082 // The transform isn't aligned, so don't snap.
3083 return {4, 4};
3086 // Skia only supports subpixel positioning to the nearest 1/4 fraction. It
3087 // would be wasteful to attempt to cache text runs with positioning that is
3088 // anymore precise than this. To prevent this cache bloat, we quantize the
3089 // transformed glyph positions to the nearest 1/4. The scaling factor for
3090 // the quantization is baked into the transform, so that if subpixel rounding
3091 // is used on a given axis, then the axis will be multiplied by 4 before
3092 // rounding. Since the quantized position is not used for rasterization, the
3093 // transform is safe to modify as such.
3094 static inline IntPoint QuantizePosition(const Matrix& aTransform,
3095 const IntPoint& aOffset,
3096 const Point& aPosition) {
3097 return RoundedToInt(aTransform.TransformPoint(aPosition)) - aOffset;
3100 // Get a quantized starting offset for the glyph buffer. We want this offset
3101 // to encapsulate the transform and buffer offset while still preserving the
3102 // relative subpixel positions of the glyphs this offset is subtracted from.
3103 static inline IntPoint QuantizeOffset(const Matrix& aTransform,
3104 const IntPoint& aQuantizeScale,
3105 const GlyphBuffer& aBuffer) {
3106 IntPoint offset =
3107 RoundedToInt(aTransform.TransformPoint(aBuffer.mGlyphs[0].mPosition));
3108 offset.x.value &= ~(aQuantizeScale.x.value - 1);
3109 offset.y.value &= ~(aQuantizeScale.y.value - 1);
3110 return offset;
3113 // Hashes a glyph buffer to a single hash value that can be used for quick
3114 // comparisons. Each glyph position is transformed and quantized before
3115 // hashing.
3116 HashNumber GlyphCacheEntry::HashGlyphs(const GlyphBuffer& aBuffer,
3117 const Matrix& aTransform,
3118 const IntPoint& aQuantizeScale) {
3119 HashNumber hash = 0;
3120 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer);
3121 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) {
3122 const Glyph& glyph = aBuffer.mGlyphs[i];
3123 hash = AddToHash(hash, glyph.mIndex);
3124 IntPoint pos = QuantizePosition(aTransform, offset, glyph.mPosition);
3125 hash = AddToHash(hash, pos.x);
3126 hash = AddToHash(hash, pos.y);
3128 return hash;
3131 // Determines if an existing glyph cache entry matches an incoming text run.
3132 inline bool GlyphCacheEntry::MatchesGlyphs(
3133 const GlyphBuffer& aBuffer, const DeviceColor& aColor,
3134 const Matrix& aTransform, const IntPoint& aQuantizeOffset,
3135 const IntPoint& aBoundsOffset, const IntRect& aClipRect, HashNumber aHash,
3136 const StrokeOptions* aStrokeOptions) {
3137 // First check if the hash matches to quickly reject the text run before any
3138 // more expensive checking. If it matches, then check if the color and
3139 // transform are the same.
3140 if (aHash != mHash || aBuffer.mNumGlyphs != mBuffer.mNumGlyphs ||
3141 aColor != mColor || !HasMatchingScale(aTransform, mTransform)) {
3142 return false;
3144 // Finally check if all glyphs and their quantized positions match.
3145 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) {
3146 const Glyph& dst = mBuffer.mGlyphs[i];
3147 const Glyph& src = aBuffer.mGlyphs[i];
3148 if (dst.mIndex != src.mIndex ||
3149 dst.mPosition != Point(QuantizePosition(aTransform, aQuantizeOffset,
3150 src.mPosition))) {
3151 return false;
3154 // Check that stroke options actually match.
3155 if (aStrokeOptions) {
3156 // If stroking, verify that the entry is also stroked with the same options.
3157 if (!(mStrokeOptions && *aStrokeOptions == *mStrokeOptions)) {
3158 return false;
3160 } else if (mStrokeOptions) {
3161 // If not stroking, check if the entry is stroked. If so, don't match.
3162 return false;
3164 // Verify that the full bounds, once translated and clipped, are equal to the
3165 // clipped bounds.
3166 return (mFullBounds + aBoundsOffset)
3167 .Intersect(aClipRect)
3168 .IsEqualEdges(GetBounds() + aBoundsOffset);
3171 GlyphCacheEntry::GlyphCacheEntry(const GlyphBuffer& aBuffer,
3172 const DeviceColor& aColor,
3173 const Matrix& aTransform,
3174 const IntPoint& aQuantizeScale,
3175 const IntRect& aBounds,
3176 const IntRect& aFullBounds, HashNumber aHash,
3177 StoredStrokeOptions* aStrokeOptions)
3178 : CacheEntryImpl<GlyphCacheEntry>(aTransform, aBounds, aHash),
3179 mColor(aColor),
3180 mFullBounds(aFullBounds),
3181 mStrokeOptions(aStrokeOptions) {
3182 // Store a copy of the glyph buffer with positions already quantized for fast
3183 // comparison later.
3184 Glyph* glyphs = new Glyph[aBuffer.mNumGlyphs];
3185 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer);
3186 // Make the bounds relative to the offset so we can add a new offset later.
3187 IntPoint boundsOffset(offset.x / aQuantizeScale.x,
3188 offset.y / aQuantizeScale.y);
3189 mBounds -= boundsOffset;
3190 mFullBounds -= boundsOffset;
3191 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) {
3192 Glyph& dst = glyphs[i];
3193 const Glyph& src = aBuffer.mGlyphs[i];
3194 dst.mIndex = src.mIndex;
3195 dst.mPosition = Point(QuantizePosition(aTransform, offset, src.mPosition));
3197 mBuffer.mGlyphs = glyphs;
3198 mBuffer.mNumGlyphs = aBuffer.mNumGlyphs;
3201 GlyphCacheEntry::~GlyphCacheEntry() { delete[] mBuffer.mGlyphs; }
3203 // Attempt to find a matching entry in the glyph cache. The caller should check
3204 // whether the contained texture handle is valid to determine if it will need to
3205 // render the text run or just reuse the cached texture.
3206 already_AddRefed<GlyphCacheEntry> GlyphCache::FindEntry(
3207 const GlyphBuffer& aBuffer, const DeviceColor& aColor,
3208 const Matrix& aTransform, const IntPoint& aQuantizeScale,
3209 const IntRect& aClipRect, HashNumber aHash,
3210 const StrokeOptions* aStrokeOptions) {
3211 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer);
3212 IntPoint boundsOffset(offset.x / aQuantizeScale.x,
3213 offset.y / aQuantizeScale.y);
3214 for (const RefPtr<GlyphCacheEntry>& entry : GetChain(aHash)) {
3215 if (entry->MatchesGlyphs(aBuffer, aColor, aTransform, offset, boundsOffset,
3216 aClipRect, aHash, aStrokeOptions)) {
3217 return do_AddRef(entry);
3220 return nullptr;
3223 // Insert a new entry in the glyph cache.
3224 already_AddRefed<GlyphCacheEntry> GlyphCache::InsertEntry(
3225 const GlyphBuffer& aBuffer, const DeviceColor& aColor,
3226 const Matrix& aTransform, const IntPoint& aQuantizeScale,
3227 const IntRect& aBounds, const IntRect& aFullBounds, HashNumber aHash,
3228 const StrokeOptions* aStrokeOptions) {
3229 StoredStrokeOptions* strokeOptions = nullptr;
3230 if (aStrokeOptions) {
3231 strokeOptions = aStrokeOptions->Clone();
3232 if (!strokeOptions) {
3233 return nullptr;
3236 RefPtr<GlyphCacheEntry> entry =
3237 new GlyphCacheEntry(aBuffer, aColor, aTransform, aQuantizeScale, aBounds,
3238 aFullBounds, aHash, strokeOptions);
3239 Insert(entry);
3240 return entry.forget();
3243 GlyphCache::GlyphCache(ScaledFont* aFont) : mFont(aFont) {}
3245 static void ReleaseGlyphCache(void* aPtr) {
3246 delete static_cast<GlyphCache*>(aPtr);
3249 void DrawTargetWebgl::SetPermitSubpixelAA(bool aPermitSubpixelAA) {
3250 DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA);
3251 mSkia->SetPermitSubpixelAA(aPermitSubpixelAA);
3254 // Check for any color glyphs contained within a rasterized BGRA8 text result.
3255 static bool CheckForColorGlyphs(const RefPtr<SourceSurface>& aSurface) {
3256 if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) {
3257 return false;
3259 RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface();
3260 if (!dataSurf) {
3261 return true;
3263 DataSourceSurface::ScopedMap map(dataSurf, DataSourceSurface::READ);
3264 if (!map.IsMapped()) {
3265 return true;
3267 IntSize size = dataSurf->GetSize();
3268 const uint8_t* data = map.GetData();
3269 int32_t stride = map.GetStride();
3270 for (int y = 0; y < size.height; y++) {
3271 const uint32_t* x = (const uint32_t*)data;
3272 const uint32_t* end = x + size.width;
3273 for (; x < end; x++) {
3274 // Verify if all components are the same as for premultiplied grayscale.
3275 uint32_t color = *x;
3276 uint32_t gray = color & 0xFF;
3277 gray |= gray << 8;
3278 gray |= gray << 16;
3279 if (color != gray) return true;
3281 data += stride;
3283 return false;
3286 // Draws glyphs to the WebGL target by trying to generate a cached texture for
3287 // the text run that can be subsequently reused to quickly render the text run
3288 // without using any software surfaces.
3289 bool DrawTargetWebgl::SharedContext::DrawGlyphsAccel(
3290 ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern,
3291 const DrawOptions& aOptions, const StrokeOptions* aStrokeOptions,
3292 bool aUseSubpixelAA) {
3293 // Whether the font may use bitmaps. If so, we need to render the glyphs with
3294 // color as grayscale bitmaps will use the color while color emoji will not,
3295 // with no easy way to know ahead of time. We currently have to check the
3296 // rasterized result to see if there are any color glyphs. To render subpixel
3297 // masks, we need to know that the rasterized result actually represents a
3298 // subpixel mask rather than try to interpret it as a normal RGBA result such
3299 // as for color emoji.
3300 bool useBitmaps = !aStrokeOptions && aFont->MayUseBitmaps();
3302 // Look for an existing glyph cache on the font. If not there, create it.
3303 GlyphCache* cache =
3304 static_cast<GlyphCache*>(aFont->GetUserData(&mGlyphCacheKey));
3305 if (!cache) {
3306 cache = new GlyphCache(aFont);
3307 aFont->AddUserData(&mGlyphCacheKey, cache, ReleaseGlyphCache);
3308 mGlyphCaches.insertFront(cache);
3310 // Hash the incoming text run and looking for a matching entry.
3311 DeviceColor color = static_cast<const ColorPattern&>(aPattern).mColor;
3312 #ifdef XP_MACOSX
3313 // On macOS, depending on whether the text is classified as light-on-dark or
3314 // dark-on-light, we may end up with different amounts of dilation applied, so
3315 // we can't use the same mask in the two circumstances, or the glyphs will be
3316 // dilated incorrectly.
3317 bool lightOnDark =
3318 useBitmaps || (color.r >= 0.33f && color.g >= 0.33f && color.b >= 0.33f &&
3319 color.r + color.g + color.b >= 2.0f);
3320 #else
3321 // On other platforms, we assume no color-dependent dilation.
3322 const bool lightOnDark = true;
3323 #endif
3324 // If the font has bitmaps, use the color directly. Otherwise, the texture
3325 // will hold a grayscale mask, so encode the key's subpixel and light-or-dark
3326 // state in the color.
3327 const Matrix& currentTransform = GetTransform();
3328 IntPoint quantizeScale = QuantizeScale(aFont, currentTransform);
3329 Matrix quantizeTransform = currentTransform;
3330 quantizeTransform.PostScale(quantizeScale.x, quantizeScale.y);
3331 HashNumber hash =
3332 GlyphCacheEntry::HashGlyphs(aBuffer, quantizeTransform, quantizeScale);
3333 DeviceColor colorOrMask =
3334 useBitmaps
3335 ? color
3336 : DeviceColor::Mask(aUseSubpixelAA ? 1 : 0, lightOnDark ? 1 : 0);
3337 IntRect clipRect(IntPoint(), mViewportSize);
3338 RefPtr<GlyphCacheEntry> entry =
3339 cache->FindEntry(aBuffer, colorOrMask, quantizeTransform, quantizeScale,
3340 clipRect, hash, aStrokeOptions);
3341 if (!entry) {
3342 // For small text runs, bounds computations can be expensive relative to the
3343 // cost of looking up a cache result. Avoid doing local bounds computations
3344 // until actually inserting the entry into the cache.
3345 Maybe<Rect> bounds = mCurrentTarget->mSkia->GetGlyphLocalBounds(
3346 aFont, aBuffer, aPattern, aStrokeOptions, aOptions);
3347 if (!bounds) {
3348 return true;
3350 // Transform the local bounds into device space so that we know how big
3351 // the cached texture will be.
3352 Rect xformBounds = currentTransform.TransformBounds(*bounds);
3353 // Check if the transform flattens out the bounds before rounding.
3354 if (xformBounds.IsEmpty()) {
3355 return true;
3357 IntRect fullBounds = RoundedOut(currentTransform.TransformBounds(*bounds));
3358 IntRect clipBounds = fullBounds.Intersect(clipRect);
3359 // Check if the bounds are completely clipped out.
3360 if (clipBounds.IsEmpty()) {
3361 return true;
3363 entry = cache->InsertEntry(aBuffer, colorOrMask, quantizeTransform,
3364 quantizeScale, clipBounds, fullBounds, hash,
3365 aStrokeOptions);
3366 if (!entry) {
3367 return false;
3371 // The bounds of the entry may have a different transform offset from the
3372 // bounds of the currently drawn text run. The entry bounds are relative to
3373 // the entry's quantized offset already, so just move the bounds to the new
3374 // offset.
3375 IntRect intBounds = entry->GetBounds();
3376 IntPoint newOffset =
3377 QuantizeOffset(quantizeTransform, quantizeScale, aBuffer);
3378 intBounds +=
3379 IntPoint(newOffset.x / quantizeScale.x, newOffset.y / quantizeScale.y);
3380 // Ensure there is a clear border around the text. This must be applied only
3381 // after clipping so that we always have some border texels for filtering.
3382 intBounds.Inflate(2);
3384 RefPtr<TextureHandle> handle = entry->GetHandle();
3385 if (handle && handle->IsValid()) {
3386 // If there is an entry with a valid cached texture handle, then try
3387 // to draw with that. If that for some reason failed, then fall back
3388 // to using the Skia target as that means we were preventing from
3389 // drawing to the WebGL context based on something other than the
3390 // texture.
3391 SurfacePattern pattern(nullptr, ExtendMode::CLAMP,
3392 Matrix::Translation(intBounds.TopLeft()));
3393 if (DrawRectAccel(Rect(intBounds), pattern, aOptions,
3394 useBitmaps ? Nothing() : Some(color), &handle, false,
3395 true, true)) {
3396 return true;
3398 } else {
3399 handle = nullptr;
3401 // If we get here, either there wasn't a cached texture handle or it
3402 // wasn't valid. Render the text run into a temporary target.
3403 RefPtr<DrawTargetSkia> textDT = new DrawTargetSkia;
3404 if (textDT->Init(intBounds.Size(),
3405 lightOnDark && !useBitmaps && !aUseSubpixelAA
3406 ? SurfaceFormat::A8
3407 : SurfaceFormat::B8G8R8A8)) {
3408 if (!lightOnDark) {
3409 // If rendering dark-on-light text, we need to clear the background to
3410 // white while using an opaque alpha value to allow this.
3411 textDT->FillRect(Rect(IntRect(IntPoint(), intBounds.Size())),
3412 ColorPattern(DeviceColor(1, 1, 1, 1)),
3413 DrawOptions(1.0f, CompositionOp::OP_OVER));
3415 textDT->SetTransform(currentTransform *
3416 Matrix::Translation(-intBounds.TopLeft()));
3417 textDT->SetPermitSubpixelAA(aUseSubpixelAA);
3418 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER,
3419 aOptions.mAntialiasMode);
3420 // If bitmaps might be used, then we have to supply the color, as color
3421 // emoji may ignore it while grayscale bitmaps may use it, with no way to
3422 // know ahead of time. Otherwise, assume the output will be a mask and
3423 // just render it white to determine intensity. Depending on whether the
3424 // text is light or dark, we render white or black text respectively.
3425 ColorPattern colorPattern(
3426 useBitmaps ? color : DeviceColor::Mask(lightOnDark ? 1 : 0, 1));
3427 if (aStrokeOptions) {
3428 textDT->StrokeGlyphs(aFont, aBuffer, colorPattern, *aStrokeOptions,
3429 drawOptions);
3430 } else {
3431 textDT->FillGlyphs(aFont, aBuffer, colorPattern, drawOptions);
3433 if (!lightOnDark) {
3434 uint8_t* data = nullptr;
3435 IntSize size;
3436 int32_t stride = 0;
3437 SurfaceFormat format = SurfaceFormat::UNKNOWN;
3438 if (!textDT->LockBits(&data, &size, &stride, &format)) {
3439 return false;
3441 uint8_t* row = data;
3442 for (int y = 0; y < size.height; ++y) {
3443 uint8_t* px = row;
3444 for (int x = 0; x < size.width; ++x) {
3445 // If rendering dark-on-light text, we need to invert the final mask
3446 // so that it is in the expected white text on transparent black
3447 // format. The alpha will be initialized to the largest of the
3448 // values.
3449 px[0] = 255 - px[0];
3450 px[1] = 255 - px[1];
3451 px[2] = 255 - px[2];
3452 px[3] = std::max(px[0], std::max(px[1], px[2]));
3453 px += 4;
3455 row += stride;
3457 textDT->ReleaseBits(data);
3459 RefPtr<SourceSurface> textSurface = textDT->Snapshot();
3460 if (textSurface) {
3461 // If we don't expect the text surface to contain color glyphs
3462 // such as from subpixel AA, then do one final check to see if
3463 // any ended up in the result. If not, extract the alpha values
3464 // from the surface so we can render it as a mask.
3465 if (textSurface->GetFormat() != SurfaceFormat::A8 &&
3466 !CheckForColorGlyphs(textSurface)) {
3467 textSurface = ExtractAlpha(textSurface, !useBitmaps);
3468 if (!textSurface) {
3469 // Failed extracting alpha for the text surface...
3470 return false;
3473 // Attempt to upload the rendered text surface into a texture
3474 // handle and draw it.
3475 SurfacePattern pattern(textSurface, ExtendMode::CLAMP,
3476 Matrix::Translation(intBounds.TopLeft()));
3477 if (DrawRectAccel(Rect(intBounds), pattern, aOptions,
3478 useBitmaps ? Nothing() : Some(color), &handle, false,
3479 true) &&
3480 handle) {
3481 // If drawing succeeded, then the text surface was uploaded to
3482 // a texture handle. Assign it to the glyph cache entry.
3483 entry->Link(handle);
3484 } else {
3485 // If drawing failed, remove the entry from the cache.
3486 entry->Unlink();
3488 return true;
3492 return false;
3495 void DrawTargetWebgl::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
3496 const Pattern& aPattern,
3497 const DrawOptions& aOptions) {
3498 if (!aFont || !aBuffer.mNumGlyphs) {
3499 return;
3502 bool useSubpixelAA = ShouldUseSubpixelAA(aFont, aOptions);
3504 if (mWebglValid && SupportsDrawOptions(aOptions) &&
3505 aPattern.GetType() == PatternType::COLOR && PrepareContext() &&
3506 mSharedContext->DrawGlyphsAccel(aFont, aBuffer, aPattern, aOptions,
3507 nullptr, useSubpixelAA)) {
3508 return;
3511 // If not able to cache the text run to a texture, then just fall back to
3512 // drawing with the Skia target.
3513 if (useSubpixelAA) {
3514 // Subpixel AA does not support layering because the subpixel masks can't
3515 // blend with the over op.
3516 MarkSkiaChanged();
3517 } else {
3518 MarkSkiaChanged(aOptions);
3520 mSkia->FillGlyphs(aFont, aBuffer, aPattern, aOptions);
3523 void DrawTargetWebgl::SharedContext::WaitForShmem(DrawTargetWebgl* aTarget) {
3524 if (mWaitForShmem) {
3525 // GetError is a sync IPDL call that forces all dispatched commands to be
3526 // flushed. Once it returns, we are certain that any commands processing
3527 // the Shmem have finished.
3528 (void)mWebgl->GetError();
3529 mWaitForShmem = false;
3530 // The sync IPDL call can cause expensive round-trips to add up over time,
3531 // so account for that here.
3532 if (aTarget) {
3533 aTarget->mProfile.OnReadback();
3538 void DrawTargetWebgl::MarkSkiaChanged(const DrawOptions& aOptions) {
3539 if (SupportsLayering(aOptions)) {
3540 WaitForShmem();
3541 if (!mSkiaValid) {
3542 // If the Skia context needs initialization, clear it and enable layering.
3543 mSkiaValid = true;
3544 if (mWebglValid) {
3545 mProfile.OnLayer();
3546 mSkiaLayer = true;
3547 mSkia->Clear();
3550 // The WebGL context is no longer up-to-date.
3551 mWebglValid = false;
3552 } else {
3553 // For other composition ops, just overwrite the Skia data.
3554 MarkSkiaChanged();
3558 // Attempts to read the contents of the WebGL context into the Skia target.
3559 void DrawTargetWebgl::ReadIntoSkia() {
3560 if (mSkiaValid) {
3561 return;
3563 if (mWebglValid) {
3564 uint8_t* data = nullptr;
3565 IntSize size;
3566 int32_t stride;
3567 SurfaceFormat format;
3568 // If there's no existing snapshot and we can successfully map the Skia
3569 // target for reading, then try to read into that.
3570 if (!mSnapshot && mSkia->LockBits(&data, &size, &stride, &format)) {
3571 (void)ReadInto(data, stride);
3572 mSkia->ReleaseBits(data);
3573 } else if (RefPtr<SourceSurface> snapshot = Snapshot()) {
3574 // Otherwise, fall back to getting a snapshot from WebGL if available
3575 // and then copying that to Skia.
3576 mSkia->CopySurface(snapshot, GetRect(), IntPoint(0, 0));
3578 // Signal that we've hit a complete software fallback.
3579 mProfile.OnFallback();
3581 mSkiaValid = true;
3582 // The Skia data is flat after reading, so disable any layering.
3583 mSkiaLayer = false;
3586 // Reads data from the WebGL context and blends it with the current Skia layer.
3587 void DrawTargetWebgl::FlattenSkia() {
3588 if (!mSkiaValid || !mSkiaLayer) {
3589 return;
3591 if (RefPtr<DataSourceSurface> base = ReadSnapshot()) {
3592 mSkia->BlendSurface(base, GetRect(), IntPoint(),
3593 CompositionOp::OP_DEST_OVER);
3595 mSkiaLayer = false;
3598 // Attempts to draw the contents of the Skia target into the WebGL context.
3599 bool DrawTargetWebgl::FlushFromSkia() {
3600 // If the WebGL context has been lost, then mark it as invalid and fail.
3601 if (mSharedContext->IsContextLost()) {
3602 mWebglValid = false;
3603 return false;
3605 // The WebGL target is already valid, so there is nothing to do.
3606 if (mWebglValid) {
3607 return true;
3609 // Ensure that DrawRect doesn't recursively call into FlushFromSkia. If
3610 // the Skia target isn't valid, then it doesn't matter what is in the the
3611 // WebGL target either, so only try to blend if there is a valid Skia target.
3612 mWebglValid = true;
3613 if (mSkiaValid) {
3614 RefPtr<SourceSurface> skiaSnapshot = mSkia->Snapshot();
3615 if (!skiaSnapshot) {
3616 // There's a valid Skia target to draw to, but for some reason there is
3617 // no available snapshot, so just keep using the Skia target.
3618 mWebglValid = false;
3619 return false;
3621 AutoRestoreContext restore(this);
3622 SurfacePattern pattern(skiaSnapshot, ExtendMode::CLAMP);
3623 // If there is a layer, blend the snapshot with the WebGL context,
3624 // otherwise copy it.
3625 if (!DrawRect(Rect(GetRect()), pattern,
3626 DrawOptions(1.0f, mSkiaLayer ? CompositionOp::OP_OVER
3627 : CompositionOp::OP_SOURCE),
3628 Nothing(), mSkiaLayer ? &mSnapshotTexture : nullptr, false,
3629 false, true, true)) {
3630 // If accelerated drawing failed for some reason, then leave the Skia
3631 // target unchanged.
3632 mWebglValid = false;
3633 return false;
3636 return true;
3639 void DrawTargetWebgl::UsageProfile::BeginFrame() {
3640 // Reset the usage profile counters for the new frame.
3641 mFallbacks = 0;
3642 mLayers = 0;
3643 mCacheMisses = 0;
3644 mCacheHits = 0;
3645 mUncachedDraws = 0;
3646 mReadbacks = 0;
3649 void DrawTargetWebgl::UsageProfile::EndFrame() {
3650 bool failed = false;
3651 // If we hit a complete fallback to software rendering, or if cache misses
3652 // were more than cutoff ratio of all requests, then we consider the frame as
3653 // having failed performance profiling.
3654 float cacheRatio =
3655 StaticPrefs::gfx_canvas_accelerated_profile_cache_miss_ratio();
3656 if (mFallbacks > 0 ||
3657 float(mCacheMisses + mReadbacks + mLayers) >
3658 cacheRatio * float(mCacheMisses + mCacheHits + mUncachedDraws +
3659 mReadbacks + mLayers)) {
3660 failed = true;
3662 if (failed) {
3663 ++mFailedFrames;
3665 ++mFrameCount;
3668 bool DrawTargetWebgl::UsageProfile::RequiresRefresh() const {
3669 // If we've rendered at least the required number of frames for a profile and
3670 // more than the cutoff ratio of frames did not meet performance criteria,
3671 // then we should stop using an accelerated canvas.
3672 uint32_t profileFrames = StaticPrefs::gfx_canvas_accelerated_profile_frames();
3673 if (!profileFrames || mFrameCount < profileFrames) {
3674 return false;
3676 float failRatio =
3677 StaticPrefs::gfx_canvas_accelerated_profile_fallback_ratio();
3678 return mFailedFrames > failRatio * mFrameCount;
3681 void DrawTargetWebgl::SharedContext::CachePrefs() {
3682 uint32_t capacity = StaticPrefs::gfx_canvas_accelerated_gpu_path_size() << 20;
3683 if (capacity != mPathVertexCapacity) {
3684 mPathVertexCapacity = capacity;
3685 if (mPathCache) {
3686 mPathCache->ClearVertexRanges();
3688 if (mPathVertexBuffer) {
3689 ResetPathVertexBuffer();
3694 // For use within CanvasRenderingContext2D, called on BorrowDrawTarget.
3695 void DrawTargetWebgl::BeginFrame(const IntRect& aPersistedRect) {
3696 if (mNeedsPresent) {
3697 mNeedsPresent = false;
3698 // If still rendering into the Skia target, switch back to the WebGL
3699 // context.
3700 if (!mWebglValid) {
3701 if (aPersistedRect.IsEmpty()) {
3702 // If nothing needs to persist, just mark the WebGL context valid.
3703 mWebglValid = true;
3704 } else {
3705 FlushFromSkia();
3709 // Check if we need to clear out any cached because of memory pressure.
3710 mSharedContext->ClearCachesIfNecessary();
3711 // Cache any prefs for the frame.
3712 mSharedContext->CachePrefs();
3713 mProfile.BeginFrame();
3716 // For use within CanvasRenderingContext2D, called on ReturnDrawTarget.
3717 void DrawTargetWebgl::EndFrame() {
3718 mProfile.EndFrame();
3719 // Ensure we're not somehow using more than the allowed texture memory.
3720 mSharedContext->PruneTextureMemory();
3721 // Signal that we're done rendering the frame in case no present occurs.
3722 mSharedContext->mWebgl->EndOfFrame();
3723 // Check if we need to clear out any cached because of memory pressure.
3724 mSharedContext->ClearCachesIfNecessary();
3725 // The framebuffer is dirty, so it needs to be copied to the swapchain.
3726 mNeedsPresent = true;
3729 Maybe<layers::SurfaceDescriptor> DrawTargetWebgl::GetFrontBuffer() {
3730 // Only try to present and retrieve the front buffer if there is a valid
3731 // WebGL framebuffer that can be sent to the compositor. Otherwise, return
3732 // nothing to try to reuse the Skia snapshot.
3733 if (mNeedsPresent) {
3734 mNeedsPresent = false;
3735 if (mWebglValid || FlushFromSkia()) {
3736 // Copy and swizzle the WebGL framebuffer to the swap chain front buffer.
3737 webgl::SwapChainOptions options;
3738 options.bgra = true;
3739 // Allow async present to be toggled on for accelerated Canvas2D
3740 // independent of WebGL via pref.
3741 options.forceAsyncPresent =
3742 StaticPrefs::gfx_canvas_accelerated_async_present();
3743 mSharedContext->mWebgl->CopyToSwapChain(mFramebuffer, options);
3746 if (mWebglValid) {
3747 return mSharedContext->mWebgl->GetFrontBuffer(mFramebuffer);
3749 return Nothing();
3752 already_AddRefed<DrawTarget> DrawTargetWebgl::CreateSimilarDrawTarget(
3753 const IntSize& aSize, SurfaceFormat aFormat) const {
3754 return mSkia->CreateSimilarDrawTarget(aSize, aFormat);
3757 bool DrawTargetWebgl::CanCreateSimilarDrawTarget(const IntSize& aSize,
3758 SurfaceFormat aFormat) const {
3759 return mSkia->CanCreateSimilarDrawTarget(aSize, aFormat);
3762 RefPtr<DrawTarget> DrawTargetWebgl::CreateClippedDrawTarget(
3763 const Rect& aBounds, SurfaceFormat aFormat) {
3764 return mSkia->CreateClippedDrawTarget(aBounds, aFormat);
3767 already_AddRefed<SourceSurface> DrawTargetWebgl::CreateSourceSurfaceFromData(
3768 unsigned char* aData, const IntSize& aSize, int32_t aStride,
3769 SurfaceFormat aFormat) const {
3770 return mSkia->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat);
3773 already_AddRefed<SourceSurface>
3774 DrawTargetWebgl::CreateSourceSurfaceFromNativeSurface(
3775 const NativeSurface& aSurface) const {
3776 return mSkia->CreateSourceSurfaceFromNativeSurface(aSurface);
3779 already_AddRefed<SourceSurface> DrawTargetWebgl::OptimizeSourceSurface(
3780 SourceSurface* aSurface) const {
3781 if (aSurface->GetType() == SurfaceType::WEBGL) {
3782 return do_AddRef(aSurface);
3784 return mSkia->OptimizeSourceSurface(aSurface);
3787 already_AddRefed<SourceSurface>
3788 DrawTargetWebgl::OptimizeSourceSurfaceForUnknownAlpha(
3789 SourceSurface* aSurface) const {
3790 return mSkia->OptimizeSourceSurfaceForUnknownAlpha(aSurface);
3793 already_AddRefed<GradientStops> DrawTargetWebgl::CreateGradientStops(
3794 GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const {
3795 return mSkia->CreateGradientStops(aStops, aNumStops, aExtendMode);
3798 already_AddRefed<FilterNode> DrawTargetWebgl::CreateFilter(FilterType aType) {
3799 return mSkia->CreateFilter(aType);
3802 void DrawTargetWebgl::DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
3803 const Point& aDestPoint,
3804 const DrawOptions& aOptions) {
3805 MarkSkiaChanged(aOptions);
3806 mSkia->DrawFilter(aNode, aSourceRect, aDestPoint, aOptions);
3809 bool DrawTargetWebgl::Draw3DTransformedSurface(SourceSurface* aSurface,
3810 const Matrix4x4& aMatrix) {
3811 MarkSkiaChanged();
3812 return mSkia->Draw3DTransformedSurface(aSurface, aMatrix);
3815 void DrawTargetWebgl::PushLayer(bool aOpaque, Float aOpacity,
3816 SourceSurface* aMask,
3817 const Matrix& aMaskTransform,
3818 const IntRect& aBounds, bool aCopyBackground) {
3819 PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds,
3820 aCopyBackground, CompositionOp::OP_OVER);
3823 void DrawTargetWebgl::PushLayerWithBlend(bool aOpaque, Float aOpacity,
3824 SourceSurface* aMask,
3825 const Matrix& aMaskTransform,
3826 const IntRect& aBounds,
3827 bool aCopyBackground,
3828 CompositionOp aCompositionOp) {
3829 MarkSkiaChanged(DrawOptions(aOpacity, aCompositionOp));
3830 mSkia->PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds,
3831 aCopyBackground, aCompositionOp);
3832 ++mLayerDepth;
3835 void DrawTargetWebgl::PopLayer() {
3836 MOZ_ASSERT(mSkiaValid);
3837 MOZ_ASSERT(mLayerDepth > 0);
3838 --mLayerDepth;
3839 mSkia->PopLayer();
3842 } // namespace mozilla::gfx