1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "DrawTargetWebglInternal.h"
8 #include "SourceSurfaceWebgl.h"
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/StaticPrefs_gfx.h"
12 #include "mozilla/gfx/AAStroke.h"
13 #include "mozilla/gfx/Blur.h"
14 #include "mozilla/gfx/DrawTargetSkia.h"
15 #include "mozilla/gfx/gfxVars.h"
16 #include "mozilla/gfx/Helpers.h"
17 #include "mozilla/gfx/HelpersSkia.h"
18 #include "mozilla/gfx/Logging.h"
19 #include "mozilla/gfx/PathSkia.h"
20 #include "mozilla/gfx/Swizzle.h"
21 #include "mozilla/layers/ImageDataSerializer.h"
22 #include "skia/include/core/SkPixmap.h"
24 #include "ClientWebGLContext.h"
25 #include "WebGLChild.h"
27 #include "gfxPlatform.h"
29 namespace mozilla::gfx
{
31 // Inserts (allocates) a rectangle of the requested size into the tree.
32 Maybe
<IntPoint
> TexturePacker::Insert(const IntSize
& aSize
) {
33 // Check if the available space could possibly fit the requested size. If
34 // not, there is no reason to continue searching within this sub-tree.
35 if (mAvailable
< std::min(aSize
.width
, aSize
.height
) ||
36 mBounds
.width
< aSize
.width
|| mBounds
.height
< aSize
.height
) {
40 // If this node has children, then try to insert into each of the children
42 Maybe
<IntPoint
> inserted
= mChildren
[0].Insert(aSize
);
44 inserted
= mChildren
[1].Insert(aSize
);
46 // If the insertion succeeded, adjust the available state to reflect the
47 // remaining space in the children.
49 mAvailable
= std::max(mChildren
[0].mAvailable
, mChildren
[1].mAvailable
);
56 // If we get here, we've encountered a leaf node. First check if its size is
57 // exactly the requested size. If so, mark the node as unavailable and return
59 if (mBounds
.Size() == aSize
) {
61 return Some(mBounds
.TopLeft());
63 // The node is larger than the requested size. Choose the axis which has the
64 // most excess space beyond the requested size and split it so that at least
65 // one of the children matches the requested size for that axis.
66 if (mBounds
.width
- aSize
.width
> mBounds
.height
- aSize
.height
) {
67 mChildren
.reset(new TexturePacker
[2]{
69 IntRect(mBounds
.x
, mBounds
.y
, aSize
.width
, mBounds
.height
)),
70 TexturePacker(IntRect(mBounds
.x
+ aSize
.width
, mBounds
.y
,
71 mBounds
.width
- aSize
.width
, mBounds
.height
))});
73 mChildren
.reset(new TexturePacker
[2]{
75 IntRect(mBounds
.x
, mBounds
.y
, mBounds
.width
, aSize
.height
)),
76 TexturePacker(IntRect(mBounds
.x
, mBounds
.y
+ aSize
.height
,
77 mBounds
.width
, mBounds
.height
- aSize
.height
))});
79 // After splitting, try to insert into the first child, which should usually
80 // be big enough to accomodate the request. Adjust the available state to the
82 Maybe
<IntPoint
> inserted
= mChildren
[0].Insert(aSize
);
83 mAvailable
= std::max(mChildren
[0].mAvailable
, mChildren
[1].mAvailable
);
87 // Removes (frees) a rectangle with the given bounds from the tree.
88 bool TexturePacker::Remove(const IntRect
& aBounds
) {
90 // If there are no children, we encountered a leaf node. Non-zero available
91 // state means that this node was already removed previously. Also, if the
92 // bounds don't contain the request, and assuming the tree was previously
93 // split during insertion, then this node is not the node we're searching
95 if (mAvailable
> 0 || !mBounds
.Contains(aBounds
)) {
98 // The bounds match exactly and it was previously inserted, so in this case
99 // we can just remove it.
100 if (mBounds
== aBounds
) {
101 mAvailable
= std::min(mBounds
.width
, mBounds
.height
);
104 // We need to split this leaf node so that it can exactly match the removed
105 // bounds. We know the leaf node at least contains the removed bounds, but
106 // needs to be subdivided until it has a child node that exactly matches.
107 // Choose the axis to split with the largest amount of excess space. Within
108 // that axis, choose the larger of the space before or after the subrect as
109 // the split point to the new children.
110 if (mBounds
.width
- aBounds
.width
> mBounds
.height
- aBounds
.height
) {
111 int split
= aBounds
.x
- mBounds
.x
> mBounds
.XMost() - aBounds
.XMost()
114 mChildren
.reset(new TexturePacker
[2]{
116 IntRect(mBounds
.x
, mBounds
.y
, split
- mBounds
.x
, mBounds
.height
),
118 TexturePacker(IntRect(split
, mBounds
.y
, mBounds
.XMost() - split
,
122 int split
= aBounds
.y
- mBounds
.y
> mBounds
.YMost() - aBounds
.YMost()
125 mChildren
.reset(new TexturePacker
[2]{
127 IntRect(mBounds
.x
, mBounds
.y
, mBounds
.width
, split
- mBounds
.y
),
130 IntRect(mBounds
.x
, split
, mBounds
.width
, mBounds
.YMost() - split
),
134 // We've encountered a branch node. Determine which of the two child nodes
135 // would possibly contain the removed bounds. We first check which axis the
136 // children were split on and then whether the removed bounds on that axis
137 // are past the start of the second child. Proceed to recurse into that
138 // child node for removal.
139 bool next
= mChildren
[0].mBounds
.x
< mChildren
[1].mBounds
.x
140 ? aBounds
.x
>= mChildren
[1].mBounds
.x
141 : aBounds
.y
>= mChildren
[1].mBounds
.y
;
142 bool removed
= mChildren
[next
? 1 : 0].Remove(aBounds
);
144 if (mChildren
[0].IsFullyAvailable() && mChildren
[1].IsFullyAvailable()) {
146 mAvailable
= std::min(mBounds
.width
, mBounds
.height
);
148 mAvailable
= std::max(mChildren
[0].mAvailable
, mChildren
[1].mAvailable
);
154 BackingTexture::BackingTexture(const IntSize
& aSize
, SurfaceFormat aFormat
,
155 const RefPtr
<WebGLTextureJS
>& aTexture
)
156 : mSize(aSize
), mFormat(aFormat
), mTexture(aTexture
) {}
158 SharedTexture::SharedTexture(const IntSize
& aSize
, SurfaceFormat aFormat
,
159 const RefPtr
<WebGLTextureJS
>& aTexture
)
160 : BackingTexture(aSize
, aFormat
, aTexture
),
161 mPacker(IntRect(IntPoint(0, 0), aSize
)) {}
163 SharedTextureHandle::SharedTextureHandle(const IntRect
& aBounds
,
164 SharedTexture
* aTexture
)
165 : mBounds(aBounds
), mTexture(aTexture
) {}
167 already_AddRefed
<SharedTextureHandle
> SharedTexture::Allocate(
168 const IntSize
& aSize
) {
169 RefPtr
<SharedTextureHandle
> handle
;
170 if (Maybe
<IntPoint
> origin
= mPacker
.Insert(aSize
)) {
171 handle
= new SharedTextureHandle(IntRect(*origin
, aSize
), this);
174 return handle
.forget();
177 bool SharedTexture::Free(const SharedTextureHandle
& aHandle
) {
178 if (aHandle
.mTexture
!= this) {
181 if (!mPacker
.Remove(aHandle
.mBounds
)) {
188 StandaloneTexture::StandaloneTexture(const IntSize
& aSize
,
189 SurfaceFormat aFormat
,
190 const RefPtr
<WebGLTextureJS
>& aTexture
)
191 : BackingTexture(aSize
, aFormat
, aTexture
) {}
193 static Atomic
<int64_t> sDrawTargetWebglCount(0);
195 DrawTargetWebgl::DrawTargetWebgl() { sDrawTargetWebglCount
++; }
197 inline void DrawTargetWebgl::SharedContext::ClearLastTexture(bool aFullClear
) {
198 mLastTexture
= nullptr;
200 mLastClipMask
= nullptr;
204 // Attempts to clear the snapshot state. If the snapshot is only referenced by
205 // this target, then it should simply be destroyed. If it is a WebGL surface in
206 // use by something else, then special cleanup such as reusing the texture or
207 // copy-on-write may be possible.
208 void DrawTargetWebgl::ClearSnapshot(bool aCopyOnWrite
, bool aNeedHandle
) {
212 mSharedContext
->ClearLastTexture();
213 if (mSnapshot
->hasOneRef() || mSnapshot
->GetType() != SurfaceType::WEBGL
) {
217 RefPtr
<SourceSurfaceWebgl
> snapshot
=
218 mSnapshot
.forget().downcast
<SourceSurfaceWebgl
>();
220 // WebGL snapshots must be notified that the framebuffer contents will be
221 // changing so that it can copy the data.
222 snapshot
->DrawTargetWillChange(aNeedHandle
);
224 // If not copying, then give the backing texture to the surface for reuse.
225 snapshot
->GiveTexture(
226 mSharedContext
->WrapSnapshot(GetSize(), GetFormat(), mTex
.forget()));
230 DrawTargetWebgl::~DrawTargetWebgl() {
231 sDrawTargetWebglCount
--;
232 ClearSnapshot(false);
233 if (mSharedContext
) {
234 if (mShmem
.IsWritable()) {
235 // Force any Skia snapshots to copy the shmem before it deallocs.
236 mSkia
->DetachAllSnapshots();
237 // Ensure we're done using the shmem before dealloc.
238 mSharedContext
->WaitForShmem(this);
239 auto* child
= mSharedContext
->mWebgl
->GetChild();
240 if (child
&& child
->CanSend()) {
241 child
->DeallocShmem(mShmem
);
244 mSharedContext
->ClearLastTexture(true);
246 mSharedContext
->mWebgl
->DeleteTexture(mClipMask
);
249 mSharedContext
->mWebgl
->DeleteFramebuffer(mFramebuffer
);
252 mSharedContext
->mWebgl
->DeleteTexture(mTex
);
254 mSharedContext
->mWebgl
->Flush(false);
258 DrawTargetWebgl::SharedContext::SharedContext() = default;
260 DrawTargetWebgl::SharedContext::~SharedContext() {
261 if (sSharedContext
.init() && sSharedContext
.get() == this) {
262 sSharedContext
.set(nullptr);
264 // Detect context loss before deletion.
266 mWebgl
->ActiveTexture(LOCAL_GL_TEXTURE0
);
269 UnlinkSurfaceTextures();
273 // Remove any SourceSurface user data associated with this TextureHandle.
274 inline void DrawTargetWebgl::SharedContext::UnlinkSurfaceTexture(
275 const RefPtr
<TextureHandle
>& aHandle
) {
276 if (SourceSurface
* surface
= aHandle
->GetSurface()) {
277 // Ensure any WebGL snapshot textures get unlinked.
278 if (surface
->GetType() == SurfaceType::WEBGL
) {
279 static_cast<SourceSurfaceWebgl
*>(surface
)->OnUnlinkTexture(this);
281 surface
->RemoveUserData(aHandle
->IsShadow() ? &mShadowTextureKey
282 : &mTextureHandleKey
);
286 // Unlinks TextureHandles from any SourceSurface user data.
287 void DrawTargetWebgl::SharedContext::UnlinkSurfaceTextures() {
288 for (RefPtr
<TextureHandle
> handle
= mTextureHandles
.getFirst(); handle
;
289 handle
= handle
->getNext()) {
290 UnlinkSurfaceTexture(handle
);
294 // Unlinks GlyphCaches from any ScaledFont user data.
295 void DrawTargetWebgl::SharedContext::UnlinkGlyphCaches() {
296 GlyphCache
* cache
= mGlyphCaches
.getFirst();
298 ScaledFont
* font
= cache
->GetFont();
299 // Access the next cache before removing the user data, as it might destroy
301 cache
= cache
->getNext();
302 font
->RemoveUserData(&mGlyphCacheKey
);
306 void DrawTargetWebgl::SharedContext::OnMemoryPressure() {
307 mShouldClearCaches
= true;
310 // Clear out the entire list of texture handles from any source.
311 void DrawTargetWebgl::SharedContext::ClearAllTextures() {
312 while (!mTextureHandles
.isEmpty()) {
313 PruneTextureHandle(mTextureHandles
.popLast());
314 --mNumTextureHandles
;
318 // Scan through the shared texture pages looking for any that are empty and
320 void DrawTargetWebgl::SharedContext::ClearEmptyTextureMemory() {
321 for (auto pos
= mSharedTextures
.begin(); pos
!= mSharedTextures
.end();) {
322 if (!(*pos
)->HasAllocatedHandles()) {
323 RefPtr
<SharedTexture
> shared
= *pos
;
324 size_t usedBytes
= shared
->UsedBytes();
325 mEmptyTextureMemory
-= usedBytes
;
326 mTotalTextureMemory
-= usedBytes
;
327 pos
= mSharedTextures
.erase(pos
);
328 mWebgl
->DeleteTexture(shared
->GetWebGLTexture());
335 // If there is a request to clear out the caches because of memory pressure,
336 // then first clear out all the texture handles in the texture cache. If there
337 // are still empty texture pages being kept around, then clear those too.
338 void DrawTargetWebgl::SharedContext::ClearCachesIfNecessary() {
339 if (!mShouldClearCaches
.exchange(false)) {
342 mZeroBuffer
= nullptr;
344 if (mEmptyTextureMemory
) {
345 ClearEmptyTextureMemory();
350 // If a non-recoverable error occurred that would stop the canvas from initing.
351 static Atomic
<bool> sContextInitError(false);
353 MOZ_THREAD_LOCAL(DrawTargetWebgl::SharedContext
*)
354 DrawTargetWebgl::sSharedContext
;
356 RefPtr
<DrawTargetWebgl::SharedContext
> DrawTargetWebgl::sMainSharedContext
;
358 // Try to initialize a new WebGL context. Verifies that the requested size does
359 // not exceed the available texture limits and that shader creation succeeded.
360 bool DrawTargetWebgl::Init(const IntSize
& size
, const SurfaceFormat format
) {
361 MOZ_ASSERT(format
== SurfaceFormat::B8G8R8A8
||
362 format
== SurfaceFormat::B8G8R8X8
);
367 if (!sSharedContext
.init()) {
371 DrawTargetWebgl::SharedContext
* sharedContext
= sSharedContext
.get();
372 if (!sharedContext
|| sharedContext
->IsContextLost()) {
373 mSharedContext
= new DrawTargetWebgl::SharedContext
;
374 if (!mSharedContext
->Initialize()) {
375 mSharedContext
= nullptr;
379 sSharedContext
.set(mSharedContext
.get());
381 if (NS_IsMainThread()) {
382 // Keep the shared context alive for the main thread by adding a ref.
383 // Ensure the ref will get cleared on shutdown so it doesn't leak.
384 if (!sMainSharedContext
) {
385 ClearOnShutdown(&sMainSharedContext
);
387 sMainSharedContext
= mSharedContext
;
390 mSharedContext
= sharedContext
;
393 if (size_t(std::max(size
.width
, size
.height
)) >
394 mSharedContext
->mMaxTextureSize
) {
398 if (!CreateFramebuffer()) {
402 auto* child
= mSharedContext
->mWebgl
->GetChild();
403 if (child
&& child
->CanSend()) {
404 size_t byteSize
= layers::ImageDataSerializer::ComputeRGBBufferSize(
405 mSize
, SurfaceFormat::B8G8R8A8
);
407 (void)child
->AllocUnsafeShmem(byteSize
, &mShmem
);
410 mSkia
= new DrawTargetSkia
;
411 if (mShmem
.IsWritable()) {
412 auto stride
= layers::ImageDataSerializer::ComputeRGBStride(
413 SurfaceFormat::B8G8R8A8
, size
.width
);
414 if (!mSkia
->Init(mShmem
.get
<uint8_t>(), size
, stride
,
415 SurfaceFormat::B8G8R8A8
, true)) {
418 } else if (!mSkia
->Init(size
, SurfaceFormat::B8G8R8A8
)) {
422 // Allocate an unclipped copy of the DT pointing to its data.
423 uint8_t* dtData
= nullptr;
425 int32_t dtStride
= 0;
426 SurfaceFormat dtFormat
= SurfaceFormat::UNKNOWN
;
427 if (!mSkia
->LockBits(&dtData
, &dtSize
, &dtStride
, &dtFormat
)) {
430 mSkiaNoClip
= new DrawTargetSkia
;
431 if (!mSkiaNoClip
->Init(dtData
, dtSize
, dtStride
, dtFormat
, true)) {
432 mSkia
->ReleaseBits(dtData
);
435 mSkia
->ReleaseBits(dtData
);
437 SetPermitSubpixelAA(IsOpaque(format
));
441 bool DrawTargetWebgl::SharedContext::Initialize() {
442 WebGLContextOptions options
= {};
443 options
.alpha
= true;
444 options
.depth
= false;
445 options
.stencil
= false;
446 options
.antialias
= false;
447 options
.preserveDrawingBuffer
= true;
448 options
.failIfMajorPerformanceCaveat
= true;
450 mWebgl
= new ClientWebGLContext(true);
451 mWebgl
->SetContextOptions(options
);
452 if (mWebgl
->SetDimensions(1, 1) != NS_OK
) {
453 // There was a non-recoverable error when trying to create a host context.
454 sContextInitError
= true;
458 if (mWebgl
->IsContextLost()) {
463 mMaxTextureSize
= mWebgl
->Limits().maxTex2dSize
;
466 mRasterizationTruncates
= mWebgl
->Vendor() == gl::GLVendor::ATI
;
471 if (!CreateShaders()) {
472 // There was a non-recoverable error when trying to init shaders.
473 sContextInitError
= true;
481 void DrawTargetWebgl::SharedContext::SetBlendState(
482 CompositionOp aOp
, const Maybe
<DeviceColor
>& aColor
) {
483 if (aOp
== mLastCompositionOp
&& mLastBlendColor
== aColor
) {
486 mLastCompositionOp
= aOp
;
487 mLastBlendColor
= aColor
;
488 // AA is not supported for all composition ops, so switching blend modes may
489 // cause a toggle in AA state. Certain ops such as OP_SOURCE require output
490 // alpha that is blended separately from AA coverage. This would require two
491 // stage blending which can incur a substantial performance penalty, so to
492 // work around this currently we just disable AA for those ops.
494 // Map the composition op to a WebGL blend mode, if possible.
497 case CompositionOp::OP_OVER
:
499 // If a color is supplied, then we blend subpixel text.
500 mWebgl
->BlendColor(aColor
->b
, aColor
->g
, aColor
->r
, 1.0f
);
501 mWebgl
->BlendFunc(LOCAL_GL_CONSTANT_COLOR
,
502 LOCAL_GL_ONE_MINUS_SRC_COLOR
);
504 mWebgl
->BlendFunc(LOCAL_GL_ONE
, LOCAL_GL_ONE_MINUS_SRC_ALPHA
);
507 case CompositionOp::OP_ADD
:
508 mWebgl
->BlendFunc(LOCAL_GL_ONE
, LOCAL_GL_ONE
);
510 case CompositionOp::OP_ATOP
:
511 mWebgl
->BlendFunc(LOCAL_GL_DST_ALPHA
, LOCAL_GL_ONE_MINUS_SRC_ALPHA
);
513 case CompositionOp::OP_SOURCE
:
515 // If a color is supplied, then we assume there is clipping or AA. This
516 // requires that we still use an over blend func with the clip/AA alpha,
517 // while filling the interior with the unaltered color. Normally this
518 // would require dual source blending, but we can emulate it with only
520 mWebgl
->BlendColor(aColor
->b
, aColor
->g
, aColor
->r
, aColor
->a
);
521 mWebgl
->BlendFunc(LOCAL_GL_CONSTANT_COLOR
,
522 LOCAL_GL_ONE_MINUS_SRC_COLOR
);
527 case CompositionOp::OP_CLEAR
:
528 // Assume the source is an alpha mask for clearing. Be careful to blend in
529 // the correct alpha if the target is opaque.
530 mWebgl
->BlendFuncSeparate(
531 LOCAL_GL_ZERO
, LOCAL_GL_ONE_MINUS_SRC_ALPHA
,
532 IsOpaque(mCurrentTarget
->GetFormat()) ? LOCAL_GL_ONE
: LOCAL_GL_ZERO
,
533 LOCAL_GL_ONE_MINUS_SRC_ALPHA
);
541 mWebgl
->Enable(LOCAL_GL_BLEND
);
543 mWebgl
->Disable(LOCAL_GL_BLEND
);
547 // Ensure the WebGL framebuffer is set to the current target.
548 bool DrawTargetWebgl::SharedContext::SetTarget(DrawTargetWebgl
* aDT
) {
549 if (!mWebgl
|| mWebgl
->IsContextLost()) {
552 if (aDT
!= mCurrentTarget
) {
553 mCurrentTarget
= aDT
;
555 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, aDT
->mFramebuffer
);
556 mViewportSize
= aDT
->GetSize();
557 mWebgl
->Viewport(0, 0, mViewportSize
.width
, mViewportSize
.height
);
563 // Replace the current clip rect with a new potentially-AA'd clip rect.
564 void DrawTargetWebgl::SharedContext::SetClipRect(const Rect
& aClipRect
) {
565 // Only invalidate the clip rect if it actually changes.
566 if (!mClipAARect
.IsEqualEdges(aClipRect
)) {
567 mClipAARect
= aClipRect
;
568 // Store the integer-aligned bounds.
569 mClipRect
= RoundedOut(aClipRect
);
573 bool DrawTargetWebgl::SharedContext::SetClipMask(
574 const RefPtr
<WebGLTextureJS
>& aTex
) {
575 if (mLastClipMask
!= aTex
) {
579 mWebgl
->ActiveTexture(LOCAL_GL_TEXTURE1
);
580 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, aTex
);
581 mWebgl
->ActiveTexture(LOCAL_GL_TEXTURE0
);
582 mLastClipMask
= aTex
;
587 bool DrawTargetWebgl::SharedContext::SetNoClipMask() {
589 return SetClipMask(mNoClipMask
);
594 mNoClipMask
= mWebgl
->CreateTexture();
598 mWebgl
->ActiveTexture(LOCAL_GL_TEXTURE1
);
599 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mNoClipMask
);
600 static const uint8_t solidMask
[4] = {0xFF, 0xFF, 0xFF, 0xFF};
602 0, LOCAL_GL_RGBA8
, {0, 0, 0}, {LOCAL_GL_RGBA
, LOCAL_GL_UNSIGNED_BYTE
},
603 {LOCAL_GL_TEXTURE_2D
,
605 gfxAlphaType::NonPremult
,
606 Some(RawBuffer(Range
<const uint8_t>(solidMask
, sizeof(solidMask
))))});
607 InitTexParameters(mNoClipMask
, false);
608 mWebgl
->ActiveTexture(LOCAL_GL_TEXTURE0
);
609 mLastClipMask
= mNoClipMask
;
613 inline bool DrawTargetWebgl::ClipStack::operator==(
614 const DrawTargetWebgl::ClipStack
& aOther
) const {
615 // Verify the transform and bounds match.
616 if (!mTransform
.FuzzyEquals(aOther
.mTransform
) ||
617 !mRect
.IsEqualInterior(aOther
.mRect
)) {
620 // Verify the paths match.
622 return !aOther
.mPath
;
625 mPath
->GetBackendType() != aOther
.mPath
->GetBackendType()) {
628 if (mPath
->GetBackendType() != BackendType::SKIA
) {
629 return mPath
== aOther
.mPath
;
631 return static_cast<const PathSkia
*>(mPath
.get())->GetPath() ==
632 static_cast<const PathSkia
*>(aOther
.mPath
.get())->GetPath();
635 // If the clip region can't be approximated by a simple clip rect, then we need
636 // to generate a clip mask that can represent the clip region per-pixel. We
637 // render to the Skia target temporarily, transparent outside the clip region,
638 // opaque inside, and upload this to a texture that can be used by the shaders.
639 bool DrawTargetWebgl::GenerateComplexClipMask() {
640 if (!mClipChanged
|| (mClipMask
&& mCachedClipStack
== mClipStack
)) {
641 mClipChanged
= false;
642 // If the clip mask was already generated, use the cached mask and bounds.
643 mSharedContext
->SetClipMask(mClipMask
);
644 mSharedContext
->SetClipRect(mClipBounds
);
648 // If the Skia target is currently being used, then we can't render the mask
652 RefPtr
<ClientWebGLContext
> webgl
= mSharedContext
->mWebgl
;
658 mClipMask
= webgl
->CreateTexture();
664 // Try to get the bounds of the clip to limit the size of the mask.
665 if (Maybe
<IntRect
> clip
= mSkia
->GetDeviceClipRect(true)) {
668 // If we can't get bounds, then just use the entire viewport.
669 mClipBounds
= GetRect();
671 mClipAARect
= Rect(mClipBounds
);
672 // If initializing the clip mask, then allocate the entire texture to ensure
673 // all pixels get filled with an empty mask regardless. Otherwise, restrict
674 // uploading to only the clip region.
675 RefPtr
<DrawTargetSkia
> dt
= new DrawTargetSkia
;
676 if (!dt
->Init(mClipBounds
.Size(), SurfaceFormat::A8
)) {
679 // Set the clip region and fill the entire inside of it
680 // with opaque white.
681 mCachedClipStack
.clear();
682 for (auto& clipStack
: mClipStack
) {
683 // Record the current state of the clip stack for this mask.
684 mCachedClipStack
.push_back(clipStack
);
686 Matrix(clipStack
.mTransform
).PostTranslate(-mClipBounds
.TopLeft()));
687 if (clipStack
.mPath
) {
688 dt
->PushClip(clipStack
.mPath
);
690 dt
->PushClipRect(clipStack
.mRect
);
693 dt
->SetTransform(Matrix::Translation(-mClipBounds
.TopLeft()));
694 dt
->FillRect(Rect(mClipBounds
), ColorPattern(DeviceColor(1, 1, 1, 1)));
695 // Bind the clip mask for uploading.
696 webgl
->ActiveTexture(LOCAL_GL_TEXTURE1
);
697 webgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mClipMask
);
699 mSharedContext
->InitTexParameters(mClipMask
, false);
701 RefPtr
<DataSourceSurface
> data
;
702 if (RefPtr
<SourceSurface
> snapshot
= dt
->Snapshot()) {
703 data
= snapshot
->GetDataSurface();
705 // Finally, upload the texture data and initialize texture storage if
707 if (init
&& mClipBounds
.Size() != mSize
) {
708 mSharedContext
->UploadSurface(nullptr, SurfaceFormat::A8
, GetRect(),
709 IntPoint(), true, true);
712 mSharedContext
->UploadSurface(data
, SurfaceFormat::A8
,
713 IntRect(IntPoint(), mClipBounds
.Size()),
714 mClipBounds
.TopLeft(), init
);
715 webgl
->ActiveTexture(LOCAL_GL_TEXTURE0
);
716 // We already bound the texture, so notify the shared context that the clip
717 // mask changed to it.
718 mSharedContext
->mLastClipMask
= mClipMask
;
719 mSharedContext
->SetClipRect(mClipBounds
);
720 // We uploaded a surface, just as if we missed the texture cache, so account
722 mProfile
.OnCacheMiss();
726 bool DrawTargetWebgl::SetSimpleClipRect() {
727 // Determine whether the clipping rectangle is simple enough to accelerate.
728 // Check if there is a device space clip rectangle available from the Skia
730 if (Maybe
<IntRect
> clip
= mSkia
->GetDeviceClipRect(false)) {
731 // If the clip is empty, leave the final integer clip rectangle empty to
732 // trivially discard the draw request.
733 // If the clip rect is larger than the viewport, just set it to the
735 if (!clip
->IsEmpty() && clip
->Contains(GetRect())) {
736 clip
= Some(GetRect());
738 mSharedContext
->SetClipRect(*clip
);
739 mSharedContext
->SetNoClipMask();
743 // There was no pixel-aligned clip rect available, so check the clip stack to
744 // see if there is an AA'd axis-aligned rectangle clip.
745 Rect
rect(GetRect());
746 for (auto& clipStack
: mClipStack
) {
747 // If clip is a path or it has a non-axis-aligned transform, then it is
749 if (clipStack
.mPath
||
750 !clipStack
.mTransform
.PreservesAxisAlignedRectangles()) {
753 // Transform the rect and intersect it with the current clip.
755 clipStack
.mTransform
.TransformBounds(clipStack
.mRect
).Intersect(rect
);
757 mSharedContext
->SetClipRect(rect
);
758 mSharedContext
->SetNoClipMask();
762 // Installs the Skia clip rectangle, if applicable, onto the shared WebGL
763 // context as well as sets the WebGL framebuffer to the current target.
764 bool DrawTargetWebgl::PrepareContext(bool aClipped
) {
766 // If no clipping requested, just set the clip rect to the viewport.
767 mSharedContext
->SetClipRect(GetRect());
768 mSharedContext
->SetNoClipMask();
769 // Ensure the clip gets reset if clipping is later requested for the target.
770 mRefreshClipState
= true;
771 } else if (mRefreshClipState
|| !mSharedContext
->IsCurrentTarget(this)) {
772 // Try to use a simple clip rect if possible. Otherwise, fall back to
773 // generating a clip mask texture that can represent complex clip regions.
774 if (!SetSimpleClipRect() && !GenerateComplexClipMask()) {
777 mClipChanged
= false;
778 mRefreshClipState
= false;
780 return mSharedContext
->SetTarget(this);
783 bool DrawTargetWebgl::SharedContext::IsContextLost() const {
784 return !mWebgl
|| mWebgl
->IsContextLost();
787 // Signal to CanvasRenderingContext2D when the WebGL context is lost.
788 bool DrawTargetWebgl::IsValid() const {
789 return mSharedContext
&& !mSharedContext
->IsContextLost();
792 already_AddRefed
<DrawTargetWebgl
> DrawTargetWebgl::Create(
793 const IntSize
& aSize
, SurfaceFormat aFormat
) {
794 if (!gfxVars::UseAcceleratedCanvas2D()) {
798 int64_t count
= sDrawTargetWebglCount
;
799 if (count
> StaticPrefs::gfx_canvas_accelerated_max_draw_target_count()) {
803 // If context initialization would fail, don't even try to create a context.
804 if (sContextInitError
) {
808 if (!Factory::AllowedSurfaceSize(aSize
)) {
812 // The interpretation of the min-size and max-size follows from the old
813 // SkiaGL prefs. First just ensure that the context is not unreasonably
815 static const int32_t kMinDimension
= 16;
816 if (std::min(aSize
.width
, aSize
.height
) < kMinDimension
) {
820 int32_t minSize
= StaticPrefs::gfx_canvas_accelerated_min_size();
821 if (aSize
.width
* aSize
.height
< minSize
* minSize
) {
825 // Maximum pref allows 3 different options:
826 // 0 means unlimited size,
827 // > 0 means use value as an absolute threshold,
828 // < 0 means use the number of screen pixels as a threshold.
829 int32_t maxSize
= StaticPrefs::gfx_canvas_accelerated_max_size();
831 if (std::max(aSize
.width
, aSize
.height
) > maxSize
) {
834 } else if (maxSize
< 0) {
835 // Default to historical mobile screen size of 980x480, like FishIEtank.
836 // In addition, allow acceleration up to this size even if the screen is
837 // smaller. A lot content expects this size to work well. See Bug 999841
838 static const int32_t kScreenPixels
= 980 * 480;
839 IntSize screenSize
= gfxPlatform::GetPlatform()->GetScreenSize();
840 if (aSize
.width
* aSize
.height
>
841 std::max(screenSize
.width
* screenSize
.height
, kScreenPixels
)) {
846 RefPtr
<DrawTargetWebgl
> dt
= new DrawTargetWebgl
;
847 if (!dt
->Init(aSize
, aFormat
) || !dt
->IsValid()) {
854 void* DrawTargetWebgl::GetNativeSurface(NativeSurfaceType aType
) {
856 case NativeSurfaceType::WEBGL_CONTEXT
:
857 // If the context is lost, then don't attempt to access it.
858 if (mSharedContext
->IsContextLost()) {
864 return mSharedContext
->mWebgl
.get();
870 // Wrap a WebGL texture holding a snapshot with a texture handle. Note that
871 // while the texture is still in use as the backing texture of a framebuffer,
872 // it's texture memory is not currently tracked with other texture handles.
873 // Once it is finally orphaned and used as a texture handle, it must be added
874 // to the resource usage totals.
875 already_AddRefed
<TextureHandle
> DrawTargetWebgl::SharedContext::WrapSnapshot(
876 const IntSize
& aSize
, SurfaceFormat aFormat
, RefPtr
<WebGLTextureJS
> aTex
) {
877 // Ensure there is enough space for the texture.
878 size_t usedBytes
= BackingTexture::UsedBytes(aFormat
, aSize
);
879 PruneTextureMemory(usedBytes
, false);
880 // Allocate a handle for the texture
881 RefPtr
<StandaloneTexture
> handle
=
882 new StandaloneTexture(aSize
, aFormat
, aTex
.forget());
883 mStandaloneTextures
.push_back(handle
);
884 mTextureHandles
.insertFront(handle
);
885 mTotalTextureMemory
+= usedBytes
;
886 mUsedTextureMemory
+= usedBytes
;
887 ++mNumTextureHandles
;
888 return handle
.forget();
891 void DrawTargetWebgl::SharedContext::SetTexFilter(WebGLTextureJS
* aTex
,
893 mWebgl
->TexParameteri(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_MAG_FILTER
,
894 aFilter
? LOCAL_GL_LINEAR
: LOCAL_GL_NEAREST
);
895 mWebgl
->TexParameteri(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_MIN_FILTER
,
896 aFilter
? LOCAL_GL_LINEAR
: LOCAL_GL_NEAREST
);
899 void DrawTargetWebgl::SharedContext::InitTexParameters(WebGLTextureJS
* aTex
,
901 mWebgl
->TexParameteri(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_WRAP_S
,
902 LOCAL_GL_CLAMP_TO_EDGE
);
903 mWebgl
->TexParameteri(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_WRAP_T
,
904 LOCAL_GL_CLAMP_TO_EDGE
);
905 SetTexFilter(aTex
, aFilter
);
908 // Copy the contents of the WebGL framebuffer into a WebGL texture.
909 already_AddRefed
<TextureHandle
> DrawTargetWebgl::SharedContext::CopySnapshot(
910 const IntRect
& aRect
, TextureHandle
* aHandle
) {
911 if (!mWebgl
|| mWebgl
->IsContextLost()) {
915 // If the target is going away, then we can just directly reuse the
916 // framebuffer texture since it will never change.
917 RefPtr
<WebGLTextureJS
> tex
= mWebgl
->CreateTexture();
922 // If copying from a non-DT source, we have to bind a scratch framebuffer for
925 if (!mScratchFramebuffer
) {
926 mScratchFramebuffer
= mWebgl
->CreateFramebuffer();
928 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mScratchFramebuffer
);
929 mWebgl
->FramebufferTexture2D(
930 LOCAL_GL_FRAMEBUFFER
, LOCAL_GL_COLOR_ATTACHMENT0
, LOCAL_GL_TEXTURE_2D
,
931 aHandle
->GetBackingTexture()->GetWebGLTexture(), 0);
934 // Create a texture to hold the copy
935 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, tex
);
936 mWebgl
->TexStorage2D(LOCAL_GL_TEXTURE_2D
, 1, LOCAL_GL_RGBA8
, aRect
.width
,
938 InitTexParameters(tex
);
939 // Copy the framebuffer into the texture
940 mWebgl
->CopyTexSubImage2D(LOCAL_GL_TEXTURE_2D
, 0, 0, 0, aRect
.x
, aRect
.y
,
941 aRect
.width
, aRect
.height
);
944 SurfaceFormat format
=
945 aHandle
? aHandle
->GetFormat() : mCurrentTarget
->GetFormat();
946 already_AddRefed
<TextureHandle
> result
=
947 WrapSnapshot(aRect
.Size(), format
, tex
.forget());
949 // Restore the actual framebuffer after reading is done.
950 if (aHandle
&& mCurrentTarget
) {
951 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mCurrentTarget
->mFramebuffer
);
957 inline DrawTargetWebgl::AutoRestoreContext::AutoRestoreContext(
958 DrawTargetWebgl
* aTarget
)
960 mClipAARect(aTarget
->mSharedContext
->mClipAARect
),
961 mLastClipMask(aTarget
->mSharedContext
->mLastClipMask
) {}
963 inline DrawTargetWebgl::AutoRestoreContext::~AutoRestoreContext() {
964 mTarget
->mSharedContext
->SetClipRect(mClipAARect
);
966 mTarget
->mSharedContext
->SetClipMask(mLastClipMask
);
968 mTarget
->mRefreshClipState
= true;
971 // Utility method to install the target before copying a snapshot.
972 already_AddRefed
<TextureHandle
> DrawTargetWebgl::CopySnapshot(
973 const IntRect
& aRect
) {
974 AutoRestoreContext
restore(this);
975 if (!PrepareContext(false)) {
978 return mSharedContext
->CopySnapshot(aRect
);
981 // Borrow a snapshot that may be used by another thread for composition. Only
982 // Skia snapshots are safe to pass around.
983 already_AddRefed
<SourceSurface
> DrawTargetWebgl::GetDataSnapshot() {
986 } else if (mSkiaLayer
) {
989 return mSkia
->Snapshot(mFormat
);
992 already_AddRefed
<SourceSurface
> DrawTargetWebgl::Snapshot() {
993 // If already using the Skia fallback, then just snapshot that.
995 return GetDataSnapshot();
998 // There's no valid Skia snapshot, so we need to get one from the WebGL
1001 // Create a copy-on-write reference to this target.
1002 mSnapshot
= new SourceSurfaceWebgl(this);
1004 return do_AddRef(mSnapshot
);
1007 // If we need to provide a snapshot for another DrawTargetWebgl that shares the
1008 // same WebGL context, then it is safe to directly return a snapshot. Otherwise,
1009 // we may be exporting to another thread and require a data snapshot.
1010 already_AddRefed
<SourceSurface
> DrawTargetWebgl::GetOptimizedSnapshot(
1011 DrawTarget
* aTarget
) {
1012 if (aTarget
&& aTarget
->GetBackendType() == BackendType::WEBGL
&&
1013 static_cast<DrawTargetWebgl
*>(aTarget
)->mSharedContext
==
1017 return GetDataSnapshot();
1020 // Read from the WebGL context into a buffer. This handles both swizzling BGRA
1021 // to RGBA and flipping the image.
1022 bool DrawTargetWebgl::SharedContext::ReadInto(uint8_t* aDstData
,
1024 SurfaceFormat aFormat
,
1025 const IntRect
& aBounds
,
1026 TextureHandle
* aHandle
) {
1027 MOZ_ASSERT(aFormat
== SurfaceFormat::B8G8R8A8
||
1028 aFormat
== SurfaceFormat::B8G8R8X8
);
1030 // If reading into a new texture, we have to bind it to a scratch framebuffer
1033 if (!mScratchFramebuffer
) {
1034 mScratchFramebuffer
= mWebgl
->CreateFramebuffer();
1036 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mScratchFramebuffer
);
1037 mWebgl
->FramebufferTexture2D(
1038 LOCAL_GL_FRAMEBUFFER
, LOCAL_GL_COLOR_ATTACHMENT0
, LOCAL_GL_TEXTURE_2D
,
1039 aHandle
->GetBackingTexture()->GetWebGLTexture(), 0);
1040 } else if (mCurrentTarget
&& mCurrentTarget
->mIsClear
) {
1041 // If reading from a target that is still clear, then avoid the readback by
1042 // just clearing the data.
1043 SkPixmap(MakeSkiaImageInfo(aBounds
.Size(), aFormat
), aDstData
, aDstStride
)
1044 .erase(IsOpaque(aFormat
) ? SK_ColorBLACK
: SK_ColorTRANSPARENT
);
1048 webgl::ReadPixelsDesc desc
;
1049 desc
.srcOffset
= *ivec2::From(aBounds
);
1050 desc
.size
= *uvec2::FromSize(aBounds
);
1051 desc
.packState
.rowLength
= aDstStride
/ 4;
1053 bool success
= false;
1054 if (mCurrentTarget
&& mCurrentTarget
->mShmem
.IsWritable() &&
1055 aDstData
== mCurrentTarget
->mShmem
.get
<uint8_t>()) {
1056 success
= mWebgl
->DoReadPixels(desc
, mCurrentTarget
->mShmem
);
1058 Range
<uint8_t> range
= {aDstData
, size_t(aDstStride
) * aBounds
.height
};
1059 success
= mWebgl
->DoReadPixels(desc
, range
);
1062 // Restore the actual framebuffer after reading is done.
1063 if (aHandle
&& mCurrentTarget
) {
1064 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mCurrentTarget
->mFramebuffer
);
1070 already_AddRefed
<DataSourceSurface
>
1071 DrawTargetWebgl::SharedContext::ReadSnapshot(TextureHandle
* aHandle
) {
1072 // Allocate a data surface, map it, and read from the WebGL context into the
1074 SurfaceFormat format
= SurfaceFormat::UNKNOWN
;
1077 format
= aHandle
->GetFormat();
1078 bounds
= aHandle
->GetBounds();
1080 format
= mCurrentTarget
->GetFormat();
1081 bounds
= mCurrentTarget
->GetRect();
1083 RefPtr
<DataSourceSurface
> surface
=
1084 Factory::CreateDataSourceSurface(bounds
.Size(), format
);
1088 DataSourceSurface::ScopedMap
dstMap(surface
, DataSourceSurface::WRITE
);
1089 if (!dstMap
.IsMapped() || !ReadInto(dstMap
.GetData(), dstMap
.GetStride(),
1090 format
, bounds
, aHandle
)) {
1093 return surface
.forget();
1096 // Utility method to install the target before reading a snapshot.
1097 bool DrawTargetWebgl::ReadInto(uint8_t* aDstData
, int32_t aDstStride
) {
1098 if (!PrepareContext(false)) {
1102 return mSharedContext
->ReadInto(aDstData
, aDstStride
, GetFormat(), GetRect());
1105 // Utility method to install the target before reading a snapshot.
1106 already_AddRefed
<DataSourceSurface
> DrawTargetWebgl::ReadSnapshot() {
1107 AutoRestoreContext
restore(this);
1108 if (!PrepareContext(false)) {
1111 mProfile
.OnReadback();
1112 return mSharedContext
->ReadSnapshot();
1115 already_AddRefed
<SourceSurface
> DrawTargetWebgl::GetBackingSurface() {
1119 void DrawTargetWebgl::DetachAllSnapshots() {
1120 mSkia
->DetachAllSnapshots();
1124 // Prepare the framebuffer for accelerated drawing. Any cached snapshots will
1125 // be invalidated if not detached and copied here. Ensure the WebGL
1126 // framebuffer's contents are updated if still somehow stored in the Skia
1128 bool DrawTargetWebgl::MarkChanged() {
1130 // Try to copy the target into a new texture if possible.
1131 ClearSnapshot(true, true);
1133 if (!mWebglValid
&& !FlushFromSkia()) {
1141 bool DrawTargetWebgl::LockBits(uint8_t** aData
, IntSize
* aSize
,
1142 int32_t* aStride
, SurfaceFormat
* aFormat
,
1143 IntPoint
* aOrigin
) {
1144 // Can only access pixels if there is valid, flattened Skia data.
1145 if (mSkiaValid
&& !mSkiaLayer
) {
1147 return mSkia
->LockBits(aData
, aSize
, aStride
, aFormat
, aOrigin
);
1152 void DrawTargetWebgl::ReleaseBits(uint8_t* aData
) {
1153 // Can only access pixels if there is valid, flattened Skia data.
1154 if (mSkiaValid
&& !mSkiaLayer
) {
1155 mSkia
->ReleaseBits(aData
);
1159 // Format is x, y, alpha
1160 static const float kRectVertexData
[12] = {0.0f
, 0.0f
, 1.0f
, 1.0f
, 0.0f
, 1.0f
,
1161 1.0f
, 1.0f
, 1.0f
, 0.0f
, 1.0f
, 1.0f
};
1163 // Orphans the contents of the path vertex buffer. The beginning of the buffer
1164 // always contains data for a simple rectangle draw to avoid needing to switch
1166 void DrawTargetWebgl::SharedContext::ResetPathVertexBuffer(bool aChanged
) {
1167 mWebgl
->BindBuffer(LOCAL_GL_ARRAY_BUFFER
, mPathVertexBuffer
.get());
1168 mWebgl
->RawBufferData(
1169 LOCAL_GL_ARRAY_BUFFER
, nullptr,
1170 std::max(size_t(mPathVertexCapacity
), sizeof(kRectVertexData
)),
1171 LOCAL_GL_DYNAMIC_DRAW
);
1172 mWebgl
->RawBufferSubData(LOCAL_GL_ARRAY_BUFFER
, 0,
1173 (const uint8_t*)kRectVertexData
,
1174 sizeof(kRectVertexData
));
1175 mPathVertexOffset
= sizeof(kRectVertexData
);
1177 mWGROutputBuffer
.reset(
1178 mPathVertexCapacity
> 0
1179 ? new (fallible
) WGR::OutputVertex
[mPathVertexCapacity
/
1180 sizeof(WGR::OutputVertex
)]
1185 // Attempts to create all shaders and resources to be used for drawing commands.
1186 // Returns whether or not this succeeded.
1187 bool DrawTargetWebgl::SharedContext::CreateShaders() {
1188 if (!mPathVertexArray
) {
1189 mPathVertexArray
= mWebgl
->CreateVertexArray();
1191 if (!mPathVertexBuffer
) {
1192 mPathVertexBuffer
= mWebgl
->CreateBuffer();
1193 mWebgl
->BindVertexArray(mPathVertexArray
.get());
1194 ResetPathVertexBuffer();
1195 mWebgl
->EnableVertexAttribArray(0);
1196 mWebgl
->VertexAttribPointer(0, 3, LOCAL_GL_FLOAT
, LOCAL_GL_FALSE
, 0, 0);
1198 if (!mSolidProgram
) {
1199 // AA is computed by using the basis vectors of the transform to determine
1200 // both the scale and orientation. The scale is then used to extrude the
1201 // rectangle outward by 1 screen-space pixel to account for the AA region.
1202 // The distance to the rectangle edges is passed to the fragment shader in
1203 // an interpolant, biased by 0.5 so it represents the desired coverage. The
1204 // minimum coverage is then chosen by the fragment shader to use as an AA
1205 // coverage value to modulate the color.
1207 u
"attribute vec3 a_vertex;\n"
1208 "uniform vec2 u_transform[3];\n"
1209 "uniform vec2 u_viewport;\n"
1210 "uniform vec4 u_clipbounds;\n"
1211 "uniform float u_aa;\n"
1212 "varying vec2 v_cliptc;\n"
1213 "varying vec4 v_clipdist;\n"
1214 "varying vec4 v_dist;\n"
1215 "varying float v_alpha;\n"
1217 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1218 " dot(u_transform[1], u_transform[1]));\n"
1219 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1220 " scale *= invScale;\n"
1221 " vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
1223 " vec2 vertex = u_transform[0] * extrude.x +\n"
1224 " u_transform[1] * extrude.y +\n"
1225 " u_transform[2];\n"
1226 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1227 " v_cliptc = vertex / u_viewport;\n"
1228 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
1229 " u_clipbounds.zw - vertex);\n"
1230 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
1231 " v_alpha = a_vertex.z;\n"
1234 u
"precision mediump float;\n"
1235 "uniform vec4 u_color;\n"
1236 "uniform sampler2D u_clipmask;\n"
1237 "varying highp vec2 v_cliptc;\n"
1238 "varying vec4 v_clipdist;\n"
1239 "varying vec4 v_dist;\n"
1240 "varying float v_alpha;\n"
1242 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1243 " vec4 dist = min(v_dist, v_clipdist);\n"
1244 " dist.xy = min(dist.xy, dist.zw);\n"
1245 " float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
1246 " gl_FragColor = clip * aa * u_color;\n"
1248 RefPtr
<WebGLShaderJS
> vsId
= mWebgl
->CreateShader(LOCAL_GL_VERTEX_SHADER
);
1249 mWebgl
->ShaderSource(*vsId
, vsSource
);
1250 mWebgl
->CompileShader(*vsId
);
1251 if (!mWebgl
->GetCompileResult(*vsId
).success
) {
1254 RefPtr
<WebGLShaderJS
> fsId
= mWebgl
->CreateShader(LOCAL_GL_FRAGMENT_SHADER
);
1255 mWebgl
->ShaderSource(*fsId
, fsSource
);
1256 mWebgl
->CompileShader(*fsId
);
1257 if (!mWebgl
->GetCompileResult(*fsId
).success
) {
1260 mSolidProgram
= mWebgl
->CreateProgram();
1261 mWebgl
->AttachShader(*mSolidProgram
, *vsId
);
1262 mWebgl
->AttachShader(*mSolidProgram
, *fsId
);
1263 mWebgl
->BindAttribLocation(*mSolidProgram
, 0, u
"a_vertex"_ns
);
1264 mWebgl
->LinkProgram(*mSolidProgram
);
1265 if (!mWebgl
->GetLinkResult(*mSolidProgram
).success
) {
1268 mSolidProgramViewport
=
1269 mWebgl
->GetUniformLocation(*mSolidProgram
, u
"u_viewport"_ns
);
1270 mSolidProgramAA
= mWebgl
->GetUniformLocation(*mSolidProgram
, u
"u_aa"_ns
);
1271 mSolidProgramTransform
=
1272 mWebgl
->GetUniformLocation(*mSolidProgram
, u
"u_transform"_ns
);
1273 mSolidProgramColor
=
1274 mWebgl
->GetUniformLocation(*mSolidProgram
, u
"u_color"_ns
);
1275 mSolidProgramClipMask
=
1276 mWebgl
->GetUniformLocation(*mSolidProgram
, u
"u_clipmask"_ns
);
1277 mSolidProgramClipBounds
=
1278 mWebgl
->GetUniformLocation(*mSolidProgram
, u
"u_clipbounds"_ns
);
1279 if (!mSolidProgramViewport
|| !mSolidProgramAA
|| !mSolidProgramTransform
||
1280 !mSolidProgramColor
|| !mSolidProgramClipMask
||
1281 !mSolidProgramClipBounds
) {
1284 mWebgl
->UseProgram(mSolidProgram
);
1285 int32_t clipMaskData
= 1;
1286 mWebgl
->UniformData(LOCAL_GL_INT
, mSolidProgramClipMask
, false,
1287 {(const uint8_t*)&clipMaskData
, sizeof(clipMaskData
)});
1290 if (!mImageProgram
) {
1292 u
"attribute vec3 a_vertex;\n"
1293 "uniform vec2 u_viewport;\n"
1294 "uniform vec4 u_clipbounds;\n"
1295 "uniform float u_aa;\n"
1296 "uniform vec2 u_transform[3];\n"
1297 "uniform vec2 u_texmatrix[3];\n"
1298 "varying vec2 v_cliptc;\n"
1299 "varying vec2 v_texcoord;\n"
1300 "varying vec4 v_clipdist;\n"
1301 "varying vec4 v_dist;\n"
1302 "varying float v_alpha;\n"
1304 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1305 " dot(u_transform[1], u_transform[1]));\n"
1306 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1307 " scale *= invScale;\n"
1308 " vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
1310 " vec2 vertex = u_transform[0] * extrude.x +\n"
1311 " u_transform[1] * extrude.y +\n"
1312 " u_transform[2];\n"
1313 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1314 " v_cliptc = vertex / u_viewport;\n"
1315 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
1316 " u_clipbounds.zw - vertex);\n"
1317 " v_texcoord = u_texmatrix[0] * extrude.x +\n"
1318 " u_texmatrix[1] * extrude.y +\n"
1319 " u_texmatrix[2];\n"
1320 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
1321 " v_alpha = a_vertex.z;\n"
1324 u
"precision mediump float;\n"
1325 "uniform vec4 u_texbounds;\n"
1326 "uniform vec4 u_color;\n"
1327 "uniform float u_swizzle;\n"
1328 "uniform sampler2D u_sampler;\n"
1329 "uniform sampler2D u_clipmask;\n"
1330 "varying highp vec2 v_cliptc;\n"
1331 "varying highp vec2 v_texcoord;\n"
1332 "varying vec4 v_clipdist;\n"
1333 "varying vec4 v_dist;\n"
1334 "varying float v_alpha;\n"
1336 " highp vec2 tc = clamp(v_texcoord, u_texbounds.xy,\n"
1337 " u_texbounds.zw);\n"
1338 " vec4 image = texture2D(u_sampler, tc);\n"
1339 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1340 " vec4 dist = min(v_dist, v_clipdist);\n"
1341 " dist.xy = min(dist.xy, dist.zw);\n"
1342 " float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
1343 " gl_FragColor = clip * aa * u_color *\n"
1344 " mix(image, image.rrrr, u_swizzle);\n"
1346 RefPtr
<WebGLShaderJS
> vsId
= mWebgl
->CreateShader(LOCAL_GL_VERTEX_SHADER
);
1347 mWebgl
->ShaderSource(*vsId
, vsSource
);
1348 mWebgl
->CompileShader(*vsId
);
1349 if (!mWebgl
->GetCompileResult(*vsId
).success
) {
1352 RefPtr
<WebGLShaderJS
> fsId
= mWebgl
->CreateShader(LOCAL_GL_FRAGMENT_SHADER
);
1353 mWebgl
->ShaderSource(*fsId
, fsSource
);
1354 mWebgl
->CompileShader(*fsId
);
1355 if (!mWebgl
->GetCompileResult(*fsId
).success
) {
1358 mImageProgram
= mWebgl
->CreateProgram();
1359 mWebgl
->AttachShader(*mImageProgram
, *vsId
);
1360 mWebgl
->AttachShader(*mImageProgram
, *fsId
);
1361 mWebgl
->BindAttribLocation(*mImageProgram
, 0, u
"a_vertex"_ns
);
1362 mWebgl
->LinkProgram(*mImageProgram
);
1363 if (!mWebgl
->GetLinkResult(*mImageProgram
).success
) {
1366 mImageProgramViewport
=
1367 mWebgl
->GetUniformLocation(*mImageProgram
, u
"u_viewport"_ns
);
1368 mImageProgramAA
= mWebgl
->GetUniformLocation(*mImageProgram
, u
"u_aa"_ns
);
1369 mImageProgramTransform
=
1370 mWebgl
->GetUniformLocation(*mImageProgram
, u
"u_transform"_ns
);
1371 mImageProgramTexMatrix
=
1372 mWebgl
->GetUniformLocation(*mImageProgram
, u
"u_texmatrix"_ns
);
1373 mImageProgramTexBounds
=
1374 mWebgl
->GetUniformLocation(*mImageProgram
, u
"u_texbounds"_ns
);
1375 mImageProgramSwizzle
=
1376 mWebgl
->GetUniformLocation(*mImageProgram
, u
"u_swizzle"_ns
);
1377 mImageProgramColor
=
1378 mWebgl
->GetUniformLocation(*mImageProgram
, u
"u_color"_ns
);
1379 mImageProgramSampler
=
1380 mWebgl
->GetUniformLocation(*mImageProgram
, u
"u_sampler"_ns
);
1381 mImageProgramClipMask
=
1382 mWebgl
->GetUniformLocation(*mImageProgram
, u
"u_clipmask"_ns
);
1383 mImageProgramClipBounds
=
1384 mWebgl
->GetUniformLocation(*mImageProgram
, u
"u_clipbounds"_ns
);
1385 if (!mImageProgramViewport
|| !mImageProgramAA
|| !mImageProgramTransform
||
1386 !mImageProgramTexMatrix
|| !mImageProgramTexBounds
||
1387 !mImageProgramSwizzle
|| !mImageProgramColor
|| !mImageProgramSampler
||
1388 !mImageProgramClipMask
|| !mImageProgramClipBounds
) {
1391 mWebgl
->UseProgram(mImageProgram
);
1392 int32_t samplerData
= 0;
1393 mWebgl
->UniformData(LOCAL_GL_INT
, mImageProgramSampler
, false,
1394 {(const uint8_t*)&samplerData
, sizeof(samplerData
)});
1395 int32_t clipMaskData
= 1;
1396 mWebgl
->UniformData(LOCAL_GL_INT
, mImageProgramClipMask
, false,
1397 {(const uint8_t*)&clipMaskData
, sizeof(clipMaskData
)});
1402 void DrawTargetWebgl::SharedContext::EnableScissor(const IntRect
& aRect
) {
1403 // Only update scissor state if it actually changes.
1404 if (!mLastScissor
.IsEqualEdges(aRect
)) {
1405 mLastScissor
= aRect
;
1406 mWebgl
->Scissor(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
);
1408 if (!mScissorEnabled
) {
1409 mScissorEnabled
= true;
1410 mWebgl
->Enable(LOCAL_GL_SCISSOR_TEST
);
1414 void DrawTargetWebgl::SharedContext::DisableScissor() {
1415 if (mScissorEnabled
) {
1416 mScissorEnabled
= false;
1417 mWebgl
->Disable(LOCAL_GL_SCISSOR_TEST
);
1421 inline ColorPattern
DrawTargetWebgl::GetClearPattern() const {
1422 return ColorPattern(
1423 DeviceColor(0.0f
, 0.0f
, 0.0f
, IsOpaque(mFormat
) ? 1.0f
: 0.0f
));
1426 // Check if the transformed rect would contain the entire viewport.
1427 inline bool DrawTargetWebgl::RectContainsViewport(const Rect
& aRect
) const {
1428 return mTransform
.PreservesAxisAlignedRectangles() &&
1429 MatrixDouble(mTransform
)
1431 RectDouble(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
))
1432 .Contains(RectDouble(GetRect()));
1435 // Ensure that the rect, after transform, is within reasonable precision limits
1436 // such that when transformed and clipped in the shader it will not round bits
1437 // from the mantissa in a way that will diverge in a noticeable way from path
1438 // geometry calculated by the path fallback.
1439 static inline bool RectInsidePrecisionLimits(const Rect
& aRect
,
1440 const Matrix
& aTransform
) {
1441 return Rect(-(1 << 20), -(1 << 20), 2 << 20, 2 << 20)
1442 .Contains(aTransform
.TransformBounds(aRect
));
1445 void DrawTargetWebgl::ClearRect(const Rect
& aRect
) {
1447 // No need to clear anything if the entire framebuffer is already clear.
1451 bool containsViewport
= RectContainsViewport(aRect
);
1452 if (containsViewport
) {
1453 // If the rect encompasses the entire viewport, just clear the viewport
1454 // instead to avoid transform issues.
1455 DrawRect(Rect(GetRect()), GetClearPattern(),
1456 DrawOptions(1.0f
, CompositionOp::OP_CLEAR
), Nothing(), nullptr,
1458 } else if (RectInsidePrecisionLimits(aRect
, mTransform
)) {
1459 // If the rect transform won't stress precision, then just use it.
1460 DrawRect(aRect
, GetClearPattern(),
1461 DrawOptions(1.0f
, CompositionOp::OP_CLEAR
));
1463 // Otherwise, using the transform in the shader may lead to inaccuracies, so
1466 mSkia
->ClearRect(aRect
);
1469 // If the clear rectangle encompasses the entire viewport and is not clipped,
1470 // then mark the target as entirely clear.
1471 if (containsViewport
&& mSharedContext
->IsCurrentTarget(this) &&
1472 !mSharedContext
->HasClipMask() &&
1473 mSharedContext
->mClipAARect
.Contains(Rect(GetRect()))) {
1478 static inline DeviceColor
PremultiplyColor(const DeviceColor
& aColor
,
1479 float aAlpha
= 1.0f
) {
1480 float a
= aColor
.a
* aAlpha
;
1481 return DeviceColor(aColor
.r
* a
, aColor
.g
* a
, aColor
.b
* a
, a
);
1484 // Attempts to create the framebuffer used for drawing and also any relevant
1485 // non-shared resources. Returns whether or not this succeeded.
1486 bool DrawTargetWebgl::CreateFramebuffer() {
1487 RefPtr
<ClientWebGLContext
> webgl
= mSharedContext
->mWebgl
;
1488 if (!mFramebuffer
) {
1489 mFramebuffer
= webgl
->CreateFramebuffer();
1492 mTex
= webgl
->CreateTexture();
1493 webgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mTex
);
1494 webgl
->TexStorage2D(LOCAL_GL_TEXTURE_2D
, 1, LOCAL_GL_RGBA8
, mSize
.width
,
1496 mSharedContext
->InitTexParameters(mTex
);
1497 webgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mFramebuffer
);
1498 webgl
->FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER
,
1499 LOCAL_GL_COLOR_ATTACHMENT0
, LOCAL_GL_TEXTURE_2D
,
1501 webgl
->Viewport(0, 0, mSize
.width
, mSize
.height
);
1502 mSharedContext
->DisableScissor();
1503 DeviceColor color
= PremultiplyColor(GetClearPattern().mColor
);
1504 webgl
->ClearColor(color
.b
, color
.g
, color
.r
, color
.a
);
1505 webgl
->Clear(LOCAL_GL_COLOR_BUFFER_BIT
);
1506 mSharedContext
->ClearTarget();
1507 mSharedContext
->ClearLastTexture();
1512 void DrawTargetWebgl::CopySurface(SourceSurface
* aSurface
,
1513 const IntRect
& aSourceRect
,
1514 const IntPoint
& aDestination
) {
1515 // Intersect the source and destination rectangles with the viewport bounds.
1517 IntRect(aDestination
, aSourceRect
.Size()).SafeIntersect(GetRect());
1518 IntRect srcRect
= destRect
- aDestination
+ aSourceRect
.TopLeft();
1519 if (srcRect
.IsEmpty()) {
1525 if (destRect
.Contains(GetRect())) {
1526 // If the the destination would override the entire layer, discard the
1529 } else if (!IsOpaque(aSurface
->GetFormat())) {
1530 // If the surface is not opaque, copying it into the layer results in
1531 // unintended blending rather than a copy to the destination.
1535 // If there is no layer, copying is safe.
1538 mSkia
->CopySurface(aSurface
, srcRect
, destRect
.TopLeft());
1542 IntRect samplingRect
;
1543 if (!mSharedContext
->IsCompatibleSurface(aSurface
)) {
1544 // If this data surface completely overwrites the framebuffer, then just
1545 // copy it to the Skia target.
1546 if (destRect
.Contains(GetRect())) {
1547 MarkSkiaChanged(true);
1548 mSkia
->DetachAllSnapshots();
1549 mSkiaNoClip
->CopySurface(aSurface
, srcRect
, destRect
.TopLeft());
1553 // CopySurface usually only samples a surface once, so don't cache the
1554 // entire surface as it is unlikely to be reused. Limit it to the used
1555 // source rectangle instead.
1556 IntRect surfaceRect
= aSurface
->GetRect();
1557 if (!srcRect
.IsEqualEdges(surfaceRect
)) {
1558 samplingRect
= srcRect
.SafeIntersect(surfaceRect
);
1562 Matrix matrix
= Matrix::Translation(destRect
.TopLeft() - srcRect
.TopLeft());
1563 SurfacePattern
pattern(aSurface
, ExtendMode::CLAMP
, matrix
,
1564 SamplingFilter::POINT
, samplingRect
);
1565 DrawRect(Rect(destRect
), pattern
, DrawOptions(1.0f
, CompositionOp::OP_SOURCE
),
1566 Nothing(), nullptr, false, false);
1569 void DrawTargetWebgl::PushClip(const Path
* aPath
) {
1570 if (aPath
&& aPath
->GetBackendType() == BackendType::SKIA
) {
1571 // Detect if the path is really just a rect to simplify caching.
1572 const PathSkia
* pathSkia
= static_cast<const PathSkia
*>(aPath
);
1573 const SkPath
& skPath
= pathSkia
->GetPath();
1574 SkRect rect
= SkRect::MakeEmpty();
1575 if (skPath
.isRect(&rect
)) {
1576 PushClipRect(SkRectToRect(rect
));
1581 mClipChanged
= true;
1582 mRefreshClipState
= true;
1583 mSkia
->PushClip(aPath
);
1585 mClipStack
.push_back({GetTransform(), Rect(), aPath
});
1588 void DrawTargetWebgl::PushClipRect(const Rect
& aRect
) {
1589 mClipChanged
= true;
1590 mRefreshClipState
= true;
1591 mSkia
->PushClipRect(aRect
);
1593 mClipStack
.push_back({GetTransform(), aRect
, nullptr});
1596 void DrawTargetWebgl::PushDeviceSpaceClipRects(const IntRect
* aRects
,
1598 mClipChanged
= true;
1599 mRefreshClipState
= true;
1600 mSkia
->PushDeviceSpaceClipRects(aRects
, aCount
);
1602 for (uint32_t i
= 0; i
< aCount
; i
++) {
1603 mClipStack
.push_back({Matrix(), Rect(aRects
[i
]), nullptr});
1607 void DrawTargetWebgl::PopClip() {
1608 mClipChanged
= true;
1609 mRefreshClipState
= true;
1612 mClipStack
.pop_back();
1615 bool DrawTargetWebgl::RemoveAllClips() {
1616 if (mClipStack
.empty()) {
1619 if (!mSkia
->RemoveAllClips()) {
1622 mClipChanged
= true;
1623 mRefreshClipState
= true;
1628 // Whether a given composition operator can be mapped to a WebGL blend mode.
1629 static inline bool SupportsDrawOptions(const DrawOptions
& aOptions
) {
1630 switch (aOptions
.mCompositionOp
) {
1631 case CompositionOp::OP_OVER
:
1632 case CompositionOp::OP_ADD
:
1633 case CompositionOp::OP_ATOP
:
1634 case CompositionOp::OP_SOURCE
:
1635 case CompositionOp::OP_CLEAR
:
1642 // Whether a pattern can be mapped to an available WebGL shader.
1643 bool DrawTargetWebgl::SharedContext::SupportsPattern(const Pattern
& aPattern
) {
1644 switch (aPattern
.GetType()) {
1645 case PatternType::COLOR
:
1647 case PatternType::SURFACE
: {
1648 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
1649 if (surfacePattern
.mExtendMode
!= ExtendMode::CLAMP
) {
1652 if (surfacePattern
.mSurface
) {
1653 // If the surface is already uploaded to a texture, then just use it.
1654 if (IsCompatibleSurface(surfacePattern
.mSurface
)) {
1658 IntSize size
= surfacePattern
.mSurface
->GetSize();
1659 // The maximum size a surface can be before triggering a fallback to
1660 // software. Bound the maximum surface size by the actual texture size
1662 int32_t maxSize
= int32_t(
1663 std::min(StaticPrefs::gfx_canvas_accelerated_max_surface_size(),
1665 // Check if either of the surface dimensions or the sampling rect,
1666 // if supplied, exceed the maximum.
1667 if (std::max(size
.width
, size
.height
) > maxSize
&&
1668 (surfacePattern
.mSamplingRect
.IsEmpty() ||
1669 std::max(surfacePattern
.mSamplingRect
.width
,
1670 surfacePattern
.mSamplingRect
.height
) > maxSize
)) {
1677 // Patterns other than colors and surfaces are currently not accelerated.
1682 // Whether a given composition operator is associative and thus allows drawing
1683 // into a separate layer that can be later composited back into the WebGL
1685 static inline bool SupportsLayering(const DrawOptions
& aOptions
) {
1686 switch (aOptions
.mCompositionOp
) {
1687 case CompositionOp::OP_OVER
:
1688 // Layering is only supported for the default source-over composition op.
1695 // When a texture handle is no longer referenced, it must mark itself unused
1696 // by unlinking its owning surface.
1697 static void ReleaseTextureHandle(void* aPtr
) {
1698 static_cast<TextureHandle
*>(aPtr
)->SetSurface(nullptr);
1701 bool DrawTargetWebgl::DrawRect(const Rect
& aRect
, const Pattern
& aPattern
,
1702 const DrawOptions
& aOptions
,
1703 Maybe
<DeviceColor
> aMaskColor
,
1704 RefPtr
<TextureHandle
>* aHandle
,
1705 bool aTransformed
, bool aClipped
,
1706 bool aAccelOnly
, bool aForceUpdate
,
1707 const StrokeOptions
* aStrokeOptions
) {
1708 // If there is nothing to draw, then don't draw...
1709 if (aRect
.IsEmpty()) {
1713 // If we're already drawing directly to the WebGL context, then we want to
1714 // continue to do so. However, if we're drawing into a Skia layer over the
1715 // WebGL context, then we need to be careful to avoid repeatedly clearing
1716 // and flushing the layer if we hit a drawing request that can be accelerated
1717 // in between layered drawing requests, as clearing and flushing the layer
1718 // can be significantly expensive when repeated. So when a Skia layer is
1719 // active, if it is possible to continue drawing into the layer, then don't
1720 // accelerate the drawing request.
1721 if (mWebglValid
|| (mSkiaLayer
&& !mLayerDepth
&&
1722 (aAccelOnly
|| !SupportsLayering(aOptions
)))) {
1723 // If we get here, either the WebGL context is being directly drawn to
1724 // or we are going to flush the Skia layer to it before doing so. The shared
1725 // context still needs to be claimed and prepared for drawing. If this
1726 // fails, we just fall back to drawing with Skia below.
1727 if (PrepareContext(aClipped
)) {
1728 // The shared context is claimed and the framebuffer is now valid, so try
1729 // accelerated drawing.
1730 return mSharedContext
->DrawRectAccel(
1731 aRect
, aPattern
, aOptions
, aMaskColor
, aHandle
, aTransformed
,
1732 aClipped
, aAccelOnly
, aForceUpdate
, aStrokeOptions
);
1736 // Either there is no valid WebGL target to draw into, or we failed to prepare
1737 // it for drawing. The only thing we can do at this point is fall back to
1738 // drawing with Skia. If the request explicitly requires accelerated drawing,
1739 // then draw nothing before returning failure.
1741 DrawRectFallback(aRect
, aPattern
, aOptions
, aMaskColor
, aTransformed
,
1742 aClipped
, aStrokeOptions
);
1747 void DrawTargetWebgl::DrawRectFallback(const Rect
& aRect
,
1748 const Pattern
& aPattern
,
1749 const DrawOptions
& aOptions
,
1750 Maybe
<DeviceColor
> aMaskColor
,
1751 bool aTransformed
, bool aClipped
,
1752 const StrokeOptions
* aStrokeOptions
) {
1753 // Invalidate the WebGL target and prepare the Skia target for drawing.
1754 MarkSkiaChanged(aOptions
);
1757 // If transforms are requested, then just translate back to FillRect.
1759 mSkia
->Mask(ColorPattern(*aMaskColor
), aPattern
, aOptions
);
1760 } else if (aStrokeOptions
) {
1761 mSkia
->StrokeRect(aRect
, aPattern
, *aStrokeOptions
, aOptions
);
1763 mSkia
->FillRect(aRect
, aPattern
, aOptions
);
1765 } else if (aClipped
) {
1766 // If no transform was requested but clipping is still required, then
1767 // temporarily reset the transform before translating to FillRect.
1768 mSkia
->SetTransform(Matrix());
1770 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
1771 if (surfacePattern
.mSamplingRect
.IsEmpty()) {
1772 mSkia
->MaskSurface(ColorPattern(*aMaskColor
), surfacePattern
.mSurface
,
1773 aRect
.TopLeft(), aOptions
);
1775 mSkia
->Mask(ColorPattern(*aMaskColor
), aPattern
, aOptions
);
1777 } else if (aStrokeOptions
) {
1778 mSkia
->StrokeRect(aRect
, aPattern
, *aStrokeOptions
, aOptions
);
1780 mSkia
->FillRect(aRect
, aPattern
, aOptions
);
1782 mSkia
->SetTransform(mTransform
);
1783 } else if (aPattern
.GetType() == PatternType::SURFACE
) {
1784 // No transform nor clipping was requested, so it is essentially just a
1786 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
1787 mSkia
->CopySurface(surfacePattern
.mSurface
,
1788 surfacePattern
.mSurface
->GetRect(),
1789 IntPoint::Round(aRect
.TopLeft()));
1795 inline already_AddRefed
<WebGLTextureJS
>
1796 DrawTargetWebgl::SharedContext::GetCompatibleSnapshot(
1797 SourceSurface
* aSurface
) const {
1798 if (aSurface
->GetType() == SurfaceType::WEBGL
) {
1799 RefPtr
<SourceSurfaceWebgl
> webglSurf
=
1800 static_cast<SourceSurfaceWebgl
*>(aSurface
);
1801 if (this == webglSurf
->mSharedContext
) {
1802 // If there is a snapshot copy in a texture handle, use that.
1803 if (webglSurf
->mHandle
) {
1805 webglSurf
->mHandle
->GetBackingTexture()->GetWebGLTexture());
1807 if (RefPtr
<DrawTargetWebgl
> webglDT
= webglSurf
->GetTarget()) {
1808 // If there is a copy-on-write reference to a target, use its backing
1809 // texture directly. This is only safe if the targets don't match, but
1810 // MarkChanged should ensure that any snapshots were copied into a
1811 // texture handle before we ever get here.
1812 if (!IsCurrentTarget(webglDT
)) {
1813 return do_AddRef(webglDT
->mTex
);
1821 inline bool DrawTargetWebgl::SharedContext::IsCompatibleSurface(
1822 SourceSurface
* aSurface
) const {
1823 return bool(RefPtr
<WebGLTextureJS
>(GetCompatibleSnapshot(aSurface
)));
1826 bool DrawTargetWebgl::SharedContext::UploadSurface(
1827 DataSourceSurface
* aData
, SurfaceFormat aFormat
, const IntRect
& aSrcRect
,
1828 const IntPoint
& aDstOffset
, bool aInit
, bool aZero
,
1829 const RefPtr
<WebGLTextureJS
>& aTex
) {
1830 webgl::TexUnpackBlobDesc texDesc
= {
1831 LOCAL_GL_TEXTURE_2D
,
1832 {uint32_t(aSrcRect
.width
), uint32_t(aSrcRect
.height
), 1}};
1834 // The surface needs to be uploaded to its backing texture either to
1835 // initialize or update the texture handle contents. Map the data
1836 // contents of the surface so it can be read.
1837 DataSourceSurface::ScopedMap
map(aData
, DataSourceSurface::READ
);
1838 if (!map
.IsMapped()) {
1841 int32_t stride
= map
.GetStride();
1842 int32_t bpp
= BytesPerPixel(aFormat
);
1843 if (mCurrentTarget
&& mCurrentTarget
->mShmem
.IsWritable() &&
1844 map
.GetData() == mCurrentTarget
->mShmem
.get
<uint8_t>()) {
1845 texDesc
.sd
= Some(layers::SurfaceDescriptorBuffer(
1846 layers::RGBDescriptor(mCurrentTarget
->mSize
, SurfaceFormat::R8G8B8A8
),
1847 mCurrentTarget
->mShmem
));
1848 texDesc
.structuredSrcSize
=
1849 uvec2::From(stride
/ bpp
, mCurrentTarget
->mSize
.height
);
1850 texDesc
.unpacking
.skipPixels
= aSrcRect
.x
;
1851 texDesc
.unpacking
.skipRows
= aSrcRect
.y
;
1852 mWaitForShmem
= true;
1854 // Get the data pointer range considering the sampling rect offset and
1856 Range
<const uint8_t> range(
1857 map
.GetData() + aSrcRect
.y
* size_t(stride
) + aSrcRect
.x
* bpp
,
1858 std::max(aSrcRect
.height
- 1, 0) * size_t(stride
) +
1859 aSrcRect
.width
* bpp
);
1860 texDesc
.cpuData
= Some(RawBuffer(range
));
1862 // If the stride happens to be 4 byte aligned, assume that is the
1863 // desired alignment regardless of format (even A8). Otherwise, we
1864 // default to byte alignment.
1865 texDesc
.unpacking
.alignmentInTypeElems
= stride
% 4 ? 1 : 4;
1866 texDesc
.unpacking
.rowLength
= stride
/ bpp
;
1868 // Create a PBO filled with zero data to initialize the texture data and
1869 // avoid slow initialization inside WebGL.
1870 MOZ_ASSERT(aSrcRect
.TopLeft() == IntPoint(0, 0));
1872 size_t(GetAlignedStride
<4>(aSrcRect
.width
, BytesPerPixel(aFormat
))) *
1874 if (!mZeroBuffer
|| size
> mZeroSize
) {
1875 mZeroBuffer
= mWebgl
->CreateBuffer();
1877 mWebgl
->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER
, mZeroBuffer
);
1878 // WebGL will zero initialize the empty buffer, so we don't send zero data
1880 mWebgl
->RawBufferData(LOCAL_GL_PIXEL_UNPACK_BUFFER
, nullptr, size
,
1881 LOCAL_GL_STATIC_DRAW
);
1883 mWebgl
->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER
, mZeroBuffer
);
1885 texDesc
.pboOffset
= Some(0);
1887 // Upload as RGBA8 to avoid swizzling during upload. Surfaces provide
1888 // data as BGRA, but we manually swizzle that in the shader. An A8
1889 // surface will be stored as an R8 texture that will also be swizzled
1892 aFormat
== SurfaceFormat::A8
? LOCAL_GL_R8
: LOCAL_GL_RGBA8
;
1894 aFormat
== SurfaceFormat::A8
? LOCAL_GL_RED
: LOCAL_GL_RGBA
;
1895 webgl::PackingInfo texPI
= {extFormat
, LOCAL_GL_UNSIGNED_BYTE
};
1896 // Do the (partial) upload for the shared or standalone texture.
1898 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, aTex
);
1900 mWebgl
->RawTexImage(0, aInit
? intFormat
: 0,
1901 {uint32_t(aDstOffset
.x
), uint32_t(aDstOffset
.y
), 0},
1902 texPI
, std::move(texDesc
));
1904 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mLastTexture
);
1906 if (!aData
&& aZero
) {
1907 mWebgl
->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER
, 0);
1912 // Allocate a new texture handle backed by either a standalone texture or as a
1913 // sub-texture of a larger shared texture.
1914 already_AddRefed
<TextureHandle
>
1915 DrawTargetWebgl::SharedContext::AllocateTextureHandle(SurfaceFormat aFormat
,
1916 const IntSize
& aSize
,
1919 RefPtr
<TextureHandle
> handle
;
1920 // Calculate the bytes that would be used by this texture handle, and prune
1921 // enough other textures to ensure we have that much usable texture space
1922 // available to allocate.
1923 size_t usedBytes
= BackingTexture::UsedBytes(aFormat
, aSize
);
1924 PruneTextureMemory(usedBytes
, false);
1925 // The requested page size for shared textures.
1926 int32_t pageSize
= int32_t(std::min(
1927 StaticPrefs::gfx_canvas_accelerated_shared_page_size(), mMaxTextureSize
));
1928 if (aAllowShared
&& std::max(aSize
.width
, aSize
.height
) <= pageSize
/ 2) {
1929 // Ensure that the surface is no bigger than a quadrant of a shared texture
1930 // page. If so, try to allocate it to a shared texture. Look for any
1931 // existing shared texture page with a matching format and allocate
1932 // from that if possible.
1933 for (auto& shared
: mSharedTextures
) {
1934 if (shared
->GetFormat() == aFormat
&&
1935 shared
->IsRenderable() == aRenderable
) {
1936 bool wasEmpty
= !shared
->HasAllocatedHandles();
1937 handle
= shared
->Allocate(aSize
);
1940 // If the page was previously empty, then deduct it from the
1941 // empty memory reserves.
1942 mEmptyTextureMemory
-= shared
->UsedBytes();
1948 // If we couldn't find an existing shared texture page with matching
1949 // format, then allocate a new page to put the request in.
1951 if (RefPtr
<WebGLTextureJS
> tex
= mWebgl
->CreateTexture()) {
1952 RefPtr
<SharedTexture
> shared
=
1953 new SharedTexture(IntSize(pageSize
, pageSize
), aFormat
, tex
);
1955 shared
->MarkRenderable();
1957 mSharedTextures
.push_back(shared
);
1958 mTotalTextureMemory
+= shared
->UsedBytes();
1959 handle
= shared
->Allocate(aSize
);
1963 // The surface wouldn't fit in a shared texture page, so we need to
1964 // allocate a standalone texture for it instead.
1965 if (RefPtr
<WebGLTextureJS
> tex
= mWebgl
->CreateTexture()) {
1966 RefPtr
<StandaloneTexture
> standalone
=
1967 new StandaloneTexture(aSize
, aFormat
, tex
);
1969 standalone
->MarkRenderable();
1971 mStandaloneTextures
.push_back(standalone
);
1972 mTotalTextureMemory
+= standalone
->UsedBytes();
1973 handle
= standalone
;
1981 // Insert the new texture handle into the front of the MRU list and
1982 // update used space for it.
1983 mTextureHandles
.insertFront(handle
);
1984 ++mNumTextureHandles
;
1985 mUsedTextureMemory
+= handle
->UsedBytes();
1987 return handle
.forget();
1990 static inline SamplingFilter
GetSamplingFilter(const Pattern
& aPattern
) {
1991 return aPattern
.GetType() == PatternType::SURFACE
1992 ? static_cast<const SurfacePattern
&>(aPattern
).mSamplingFilter
1993 : SamplingFilter::GOOD
;
1996 static inline bool UseNearestFilter(const Pattern
& aPattern
) {
1997 return GetSamplingFilter(aPattern
) == SamplingFilter::POINT
;
2000 // Determine if the rectangle is still axis-aligned and pixel-aligned.
2001 static inline Maybe
<IntRect
> IsAlignedRect(bool aTransformed
,
2002 const Matrix
& aCurrentTransform
,
2003 const Rect
& aRect
) {
2004 if (!aTransformed
|| aCurrentTransform
.HasOnlyIntegerTranslation()) {
2005 auto intRect
= RoundedToInt(aRect
);
2006 if (aRect
.WithinEpsilonOf(Rect(intRect
), 1.0e-3f
)) {
2008 intRect
+= RoundedToInt(aCurrentTransform
.GetTranslation());
2010 return Some(intRect
);
2016 template <class T
, size_t N
>
2017 void DrawTargetWebgl::SharedContext::MaybeUniformData(
2018 GLenum aFuncElemType
, const WebGLUniformLocationJS
* const aLoc
,
2019 const Array
<T
, N
>& aData
, Maybe
<Array
<T
, N
>>& aCached
) {
2020 if (aCached
.isNothing() || !(*aCached
== aData
)) {
2021 aCached
= Some(aData
);
2022 Span
<const uint8_t> bytes
= AsBytes(Span(aData
));
2023 // We currently always pass false for transpose. If in the future we need
2024 // support for transpose then caching needs to take that in to account.
2025 mWebgl
->UniformData(aFuncElemType
, aLoc
, false, bytes
);
2029 // Common rectangle and pattern drawing function shared by many DrawTarget
2030 // commands. If aMaskColor is specified, the provided surface pattern will be
2031 // treated as a mask. If aHandle is specified, then the surface pattern's
2032 // texture will be cached in the supplied handle, as opposed to using the
2033 // surface's user data. If aTransformed or aClipped are false, then transforms
2034 // and/or clipping will be disabled. If aAccelOnly is specified, then this
2035 // function will return before it would have otherwise drawn without
2036 // acceleration. If aForceUpdate is specified, then the provided texture handle
2037 // will be respecified with the provided surface.
2038 bool DrawTargetWebgl::SharedContext::DrawRectAccel(
2039 const Rect
& aRect
, const Pattern
& aPattern
, const DrawOptions
& aOptions
,
2040 Maybe
<DeviceColor
> aMaskColor
, RefPtr
<TextureHandle
>* aHandle
,
2041 bool aTransformed
, bool aClipped
, bool aAccelOnly
, bool aForceUpdate
,
2042 const StrokeOptions
* aStrokeOptions
, const PathVertexRange
* aVertexRange
) {
2043 // If the rect or clip rect is empty, then there is nothing to draw.
2044 if (aRect
.IsEmpty() || mClipRect
.IsEmpty()) {
2048 // Check if the drawing options and the pattern support acceleration. Also
2049 // ensure the framebuffer is prepared for drawing. If not, fall back to using
2051 if (!SupportsDrawOptions(aOptions
) || !SupportsPattern(aPattern
) ||
2052 aStrokeOptions
|| !mCurrentTarget
->MarkChanged()) {
2053 // If only accelerated drawing was requested, bail out without software
2054 // drawing fallback.
2056 MOZ_ASSERT(!aVertexRange
);
2057 mCurrentTarget
->DrawRectFallback(aRect
, aPattern
, aOptions
, aMaskColor
,
2058 aTransformed
, aClipped
, aStrokeOptions
);
2063 const Matrix
& currentTransform
= GetTransform();
2065 if (aOptions
.mCompositionOp
== CompositionOp::OP_SOURCE
&& aTransformed
&&
2067 (HasClipMask() || !currentTransform
.PreservesAxisAlignedRectangles() ||
2068 !currentTransform
.TransformBounds(aRect
).Contains(Rect(mClipAARect
)) ||
2069 (aPattern
.GetType() == PatternType::SURFACE
&&
2070 !IsAlignedRect(aTransformed
, currentTransform
, aRect
)))) {
2071 // Clear outside the mask region for masks that are not bounded by clip.
2072 return DrawRectAccel(Rect(mClipRect
), ColorPattern(DeviceColor(0, 0, 0, 0)),
2073 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
,
2074 aOptions
.mAntialiasMode
),
2075 Nothing(), nullptr, false, aClipped
, aAccelOnly
) &&
2076 DrawRectAccel(aRect
, aPattern
,
2077 DrawOptions(aOptions
.mAlpha
, CompositionOp::OP_ADD
,
2078 aOptions
.mAntialiasMode
),
2079 aMaskColor
, aHandle
, aTransformed
, aClipped
,
2080 aAccelOnly
, aForceUpdate
, aStrokeOptions
,
2083 if (aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
&&
2084 aPattern
.GetType() == PatternType::SURFACE
&& !aMaskColor
) {
2085 // If the surface being drawn with clear is not a mask, then its contents
2086 // needs to be ignored. Just use a color pattern instead.
2087 return DrawRectAccel(aRect
, ColorPattern(DeviceColor(1, 1, 1, 1)), aOptions
,
2088 Nothing(), aHandle
, aTransformed
, aClipped
, aAccelOnly
,
2089 aForceUpdate
, aStrokeOptions
, aVertexRange
);
2092 // Set up the scissor test to reflect the clipping rectangle, if supplied.
2093 if (!mClipRect
.Contains(IntRect(IntPoint(), mViewportSize
))) {
2094 EnableScissor(mClipRect
);
2099 bool success
= false;
2101 // Now try to actually draw the pattern...
2102 switch (aPattern
.GetType()) {
2103 case PatternType::COLOR
: {
2104 if (!aVertexRange
) {
2105 // Only an uncached draw if not using the vertex cache.
2106 mCurrentTarget
->mProfile
.OnUncachedDraw();
2108 DeviceColor color
= PremultiplyColor(
2109 static_cast<const ColorPattern
&>(aPattern
).mColor
, aOptions
.mAlpha
);
2110 if (((color
.a
== 1.0f
&&
2111 aOptions
.mCompositionOp
== CompositionOp::OP_OVER
) ||
2112 aOptions
.mCompositionOp
== CompositionOp::OP_SOURCE
||
2113 aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
) &&
2114 !aStrokeOptions
&& !aVertexRange
&& !HasClipMask() &&
2115 mClipAARect
.IsEqualEdges(Rect(mClipRect
))) {
2116 // Certain color patterns can be mapped to scissored clears. The
2117 // composition op must effectively overwrite the destination, and the
2118 // transform must map to an axis-aligned integer rectangle.
2119 if (Maybe
<IntRect
> intRect
=
2120 IsAlignedRect(aTransformed
, currentTransform
, aRect
)) {
2121 // Only use a clear if the area is larger than a quarter or the
2123 if (intRect
->Area() >=
2124 (mViewportSize
.width
/ 2) * (mViewportSize
.height
/ 2)) {
2125 if (!intRect
->Contains(mClipRect
)) {
2126 EnableScissor(intRect
->Intersect(mClipRect
));
2128 if (aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
) {
2130 PremultiplyColor(mCurrentTarget
->GetClearPattern().mColor
);
2132 mWebgl
->ClearColor(color
.b
, color
.g
, color
.r
, color
.a
);
2133 mWebgl
->Clear(LOCAL_GL_COLOR_BUFFER_BIT
);
2139 // Map the composition op to a WebGL blend mode, if possible.
2140 Maybe
<DeviceColor
> blendColor
;
2141 if (aOptions
.mCompositionOp
== CompositionOp::OP_SOURCE
||
2142 aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
) {
2143 // The source operator can support clipping and AA by emulating it with
2144 // the over op. Supply the color with blend state, and set the shader
2145 // color to white, to avoid needing dual-source blending.
2146 blendColor
= Some(color
);
2147 // Both source and clear operators should output a mask from the shader.
2148 color
= DeviceColor(1, 1, 1, 1);
2150 SetBlendState(aOptions
.mCompositionOp
, blendColor
);
2151 // Since it couldn't be mapped to a scissored clear, we need to use the
2152 // solid color shader with supplied transform.
2153 if (mLastProgram
!= mSolidProgram
) {
2154 mWebgl
->UseProgram(mSolidProgram
);
2155 mLastProgram
= mSolidProgram
;
2157 Array
<float, 2> viewportData
= {float(mViewportSize
.width
),
2158 float(mViewportSize
.height
)};
2159 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramViewport
, viewportData
,
2160 mSolidProgramUniformState
.mViewport
);
2162 // Generated paths provide their own AA as vertex alpha.
2163 Array
<float, 1> aaData
= {aVertexRange
? 0.0f
: 1.0f
};
2164 MaybeUniformData(LOCAL_GL_FLOAT
, mSolidProgramAA
, aaData
,
2165 mSolidProgramUniformState
.mAA
);
2167 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel
2169 Array
<float, 4> clipData
= {mClipAARect
.x
- 0.5f
, mClipAARect
.y
- 0.5f
,
2170 mClipAARect
.XMost() + 0.5f
,
2171 mClipAARect
.YMost() + 0.5f
};
2172 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramClipBounds
, clipData
,
2173 mSolidProgramUniformState
.mClipBounds
);
2175 Array
<float, 4> colorData
= {color
.b
, color
.g
, color
.r
, color
.a
};
2176 Matrix
xform(aRect
.width
, 0.0f
, 0.0f
, aRect
.height
, aRect
.x
, aRect
.y
);
2178 xform
*= currentTransform
;
2180 Array
<float, 6> xformData
= {xform
._11
, xform
._12
, xform
._21
,
2181 xform
._22
, xform
._31
, xform
._32
};
2182 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramTransform
, xformData
,
2183 mSolidProgramUniformState
.mTransform
);
2185 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramColor
, colorData
,
2186 mSolidProgramUniformState
.mColor
);
2188 // Finally draw the colored rectangle.
2190 // If there's a vertex range, then we need to draw triangles within from
2191 // generated from a path stored in the path vertex buffer.
2192 mWebgl
->DrawArrays(LOCAL_GL_TRIANGLES
, GLint(aVertexRange
->mOffset
),
2193 GLsizei(aVertexRange
->mLength
));
2195 // Otherwise we're drawing a simple filled rectangle.
2196 mWebgl
->DrawArrays(LOCAL_GL_TRIANGLE_FAN
, 0, 4);
2201 case PatternType::SURFACE
: {
2202 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
2203 // If a texture handle was supplied, or if the surface already has an
2204 // assigned texture handle stashed in its used data, try to use it.
2205 RefPtr
<TextureHandle
> handle
=
2206 aHandle
? aHandle
->get()
2207 : (surfacePattern
.mSurface
2208 ? static_cast<TextureHandle
*>(
2209 surfacePattern
.mSurface
->GetUserData(
2210 &mTextureHandleKey
))
2214 SurfaceFormat format
;
2215 // Check if the found handle is still valid and if its sampling rect
2216 // matches the requested sampling rect.
2217 if (handle
&& handle
->IsValid() &&
2218 (surfacePattern
.mSamplingRect
.IsEmpty() ||
2219 handle
->GetSamplingRect().IsEqualEdges(
2220 surfacePattern
.mSamplingRect
))) {
2221 texSize
= handle
->GetSize();
2222 format
= handle
->GetFormat();
2223 offset
= handle
->GetSamplingOffset();
2225 // Otherwise, there is no handle that can be used yet, so extract
2226 // information from the surface pattern.
2228 if (!surfacePattern
.mSurface
) {
2229 // If there was no actual surface supplied, then we tried to draw
2230 // using a texture handle, but the texture handle wasn't valid.
2233 texSize
= surfacePattern
.mSurface
->GetSize();
2234 format
= surfacePattern
.mSurface
->GetFormat();
2235 if (!surfacePattern
.mSamplingRect
.IsEmpty()) {
2236 texSize
= surfacePattern
.mSamplingRect
.Size();
2237 offset
= surfacePattern
.mSamplingRect
.TopLeft();
2241 // We need to be able to transform from local space into texture space.
2242 Matrix invMatrix
= surfacePattern
.mMatrix
;
2243 // If drawing a pre-transformed vertex range, then we need to ensure the
2244 // user-space pattern is still transformed to screen-space.
2245 if (aVertexRange
&& !aTransformed
) {
2246 invMatrix
*= currentTransform
;
2248 if (!invMatrix
.Invert()) {
2252 RefPtr
<WebGLTextureJS
> tex
;
2254 IntSize backingSize
;
2255 RefPtr
<DataSourceSurface
> data
;
2258 data
= surfacePattern
.mSurface
->GetDataSurface();
2262 // The size of the texture may change if we update contents.
2263 mUsedTextureMemory
-= handle
->UsedBytes();
2264 handle
->UpdateSize(texSize
);
2265 mUsedTextureMemory
+= handle
->UsedBytes();
2266 handle
->SetSamplingOffset(surfacePattern
.mSamplingRect
.TopLeft());
2268 // If using an existing handle, move it to the front of the MRU list.
2270 mTextureHandles
.insertFront(handle
);
2271 } else if ((tex
= GetCompatibleSnapshot(surfacePattern
.mSurface
))) {
2272 backingSize
= surfacePattern
.mSurface
->GetSize();
2273 bounds
= IntRect(offset
, texSize
);
2274 // Count reusing a snapshot texture (no readback) as a cache hit.
2275 mCurrentTarget
->mProfile
.OnCacheHit();
2277 // If we get here, we need a data surface for a texture upload.
2278 data
= surfacePattern
.mSurface
->GetDataSurface();
2282 // There is no existing handle. Try to allocate a new one. If the
2283 // surface size may change via a forced update, then don't allocate
2284 // from a shared texture page.
2285 handle
= AllocateTextureHandle(format
, texSize
, !aForceUpdate
);
2290 // Link the handle to the surface's user data.
2291 handle
->SetSamplingOffset(surfacePattern
.mSamplingRect
.TopLeft());
2295 handle
->SetSurface(surfacePattern
.mSurface
);
2296 surfacePattern
.mSurface
->AddUserData(&mTextureHandleKey
, handle
.get(),
2297 ReleaseTextureHandle
);
2301 // Map the composition op to a WebGL blend mode, if possible. If there is
2302 // a mask color and a texture with multiple channels, assume subpixel
2303 // blending. If we encounter the source op here, then assume the surface
2304 // is opaque (non-opaque is handled above) and emulate it with over.
2305 SetBlendState(aOptions
.mCompositionOp
,
2306 format
!= SurfaceFormat::A8
? aMaskColor
: Nothing());
2307 // Switch to the image shader and set up relevant transforms.
2308 if (mLastProgram
!= mImageProgram
) {
2309 mWebgl
->UseProgram(mImageProgram
);
2310 mLastProgram
= mImageProgram
;
2313 Array
<float, 2> viewportData
= {float(mViewportSize
.width
),
2314 float(mViewportSize
.height
)};
2315 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mImageProgramViewport
, viewportData
,
2316 mImageProgramUniformState
.mViewport
);
2318 // AA is not supported for OP_SOURCE. Generated paths provide their own
2319 // AA as vertex alpha.
2320 Array
<float, 1> aaData
= {
2321 mLastCompositionOp
== CompositionOp::OP_SOURCE
|| aVertexRange
2324 MaybeUniformData(LOCAL_GL_FLOAT
, mImageProgramAA
, aaData
,
2325 mImageProgramUniformState
.mAA
);
2327 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel
2329 Array
<float, 4> clipData
= {mClipAARect
.x
- 0.5f
, mClipAARect
.y
- 0.5f
,
2330 mClipAARect
.XMost() + 0.5f
,
2331 mClipAARect
.YMost() + 0.5f
};
2332 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mImageProgramClipBounds
, clipData
,
2333 mImageProgramUniformState
.mClipBounds
);
2336 mLastCompositionOp
== CompositionOp::OP_CLEAR
2337 ? DeviceColor(1, 1, 1, 1)
2339 aMaskColor
&& format
!= SurfaceFormat::A8
2340 ? DeviceColor::Mask(1.0f
, aMaskColor
->a
)
2341 : aMaskColor
.valueOr(DeviceColor(1, 1, 1, 1)),
2343 Array
<float, 4> colorData
= {color
.b
, color
.g
, color
.r
, color
.a
};
2344 Array
<float, 1> swizzleData
= {format
== SurfaceFormat::A8
? 1.0f
: 0.0f
};
2345 Matrix
xform(aRect
.width
, 0.0f
, 0.0f
, aRect
.height
, aRect
.x
, aRect
.y
);
2347 xform
*= currentTransform
;
2349 Array
<float, 6> xformData
= {xform
._11
, xform
._12
, xform
._21
,
2350 xform
._22
, xform
._31
, xform
._32
};
2351 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mImageProgramTransform
, xformData
,
2352 mImageProgramUniformState
.mTransform
);
2354 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mImageProgramColor
, colorData
,
2355 mImageProgramUniformState
.mColor
);
2357 MaybeUniformData(LOCAL_GL_FLOAT
, mImageProgramSwizzle
, swizzleData
,
2358 mImageProgramUniformState
.mSwizzle
);
2360 // Start binding the WebGL state for the texture.
2361 BackingTexture
* backing
= nullptr;
2363 backing
= handle
->GetBackingTexture();
2365 tex
= backing
->GetWebGLTexture();
2367 bounds
= handle
->GetBounds();
2368 backingSize
= backing
->GetSize();
2370 if (mLastTexture
!= tex
) {
2371 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, tex
);
2375 if (backing
&& !backing
->IsInitialized()) {
2376 // If this is the first time the texture is used, we need to initialize
2377 // the clamping and filtering state.
2378 backing
->MarkInitialized();
2379 InitTexParameters(tex
);
2380 if (texSize
!= backingSize
) {
2381 // If this is a shared texture handle whose actual backing texture is
2382 // larger than it, then we need to allocate the texture page to the
2383 // full backing size before we can do a partial upload of the surface.
2384 UploadSurface(nullptr, format
, IntRect(IntPoint(), backingSize
),
2385 IntPoint(), true, true);
2390 UploadSurface(data
, format
, IntRect(offset
, texSize
), bounds
.TopLeft(),
2391 texSize
== backingSize
);
2392 // Signal that we had to upload new data to the texture cache.
2393 mCurrentTarget
->mProfile
.OnCacheMiss();
2395 // Signal that we are reusing data from the texture cache.
2396 mCurrentTarget
->mProfile
.OnCacheHit();
2399 // Set up the texture coordinate matrix to map from the input rectangle to
2400 // the backing texture subrect.
2401 Size
backingSizeF(backingSize
);
2402 Matrix
uvMatrix(aRect
.width
, 0.0f
, 0.0f
, aRect
.height
, aRect
.x
, aRect
.y
);
2403 uvMatrix
*= invMatrix
;
2404 uvMatrix
*= Matrix(1.0f
/ backingSizeF
.width
, 0.0f
, 0.0f
,
2405 1.0f
/ backingSizeF
.height
,
2406 float(bounds
.x
- offset
.x
) / backingSizeF
.width
,
2407 float(bounds
.y
- offset
.y
) / backingSizeF
.height
);
2408 Array
<float, 6> uvData
= {uvMatrix
._11
, uvMatrix
._12
, uvMatrix
._21
,
2409 uvMatrix
._22
, uvMatrix
._31
, uvMatrix
._32
};
2410 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mImageProgramTexMatrix
, uvData
,
2411 mImageProgramUniformState
.mTexMatrix
);
2413 // Clamp sampling to within the bounds of the backing texture subrect.
2414 Array
<float, 4> texBounds
= {
2415 (bounds
.x
+ 0.5f
) / backingSizeF
.width
,
2416 (bounds
.y
+ 0.5f
) / backingSizeF
.height
,
2417 (bounds
.XMost() - 0.5f
) / backingSizeF
.width
,
2418 (bounds
.YMost() - 0.5f
) / backingSizeF
.height
,
2420 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mImageProgramTexBounds
, texBounds
,
2421 mImageProgramUniformState
.mTexBounds
);
2423 // Ensure we use nearest filtering when no antialiasing is requested.
2424 if (UseNearestFilter(surfacePattern
)) {
2425 SetTexFilter(tex
, false);
2428 // Finally draw the image rectangle.
2430 // If there's a vertex range, then we need to draw triangles within from
2431 // generated from a path stored in the path vertex buffer.
2432 mWebgl
->DrawArrays(LOCAL_GL_TRIANGLES
, GLint(aVertexRange
->mOffset
),
2433 GLsizei(aVertexRange
->mLength
));
2435 // Otherwise we're drawing a simple filled rectangle.
2436 mWebgl
->DrawArrays(LOCAL_GL_TRIANGLE_FAN
, 0, 4);
2439 // Restore the default linear filter if overridden.
2440 if (UseNearestFilter(surfacePattern
)) {
2441 SetTexFilter(tex
, true);
2448 gfxWarning() << "Unknown DrawTargetWebgl::DrawRect pattern type: "
2449 << (int)aPattern
.GetType();
2452 // mWebgl->Disable(LOCAL_GL_BLEND);
2457 bool DrawTargetWebgl::SharedContext::RemoveSharedTexture(
2458 const RefPtr
<SharedTexture
>& aTexture
) {
2460 std::find(mSharedTextures
.begin(), mSharedTextures
.end(), aTexture
);
2461 if (pos
== mSharedTextures
.end()) {
2464 // Keep around a reserve of empty pages to avoid initialization costs from
2465 // allocating shared pages. If still below the limit of reserved pages, then
2466 // just add it to the reserve. Otherwise, erase the empty texture page.
2467 size_t maxBytes
= StaticPrefs::gfx_canvas_accelerated_reserve_empty_cache()
2469 size_t usedBytes
= aTexture
->UsedBytes();
2470 if (mEmptyTextureMemory
+ usedBytes
<= maxBytes
) {
2471 mEmptyTextureMemory
+= usedBytes
;
2473 mTotalTextureMemory
-= usedBytes
;
2474 mSharedTextures
.erase(pos
);
2476 mWebgl
->DeleteTexture(aTexture
->GetWebGLTexture());
2481 void SharedTextureHandle::Cleanup(DrawTargetWebgl::SharedContext
& aContext
) {
2482 mTexture
->Free(*this);
2484 // Check if the shared handle's owning page has no more allocated handles
2485 // after we freed it. If so, remove the empty shared texture page also.
2486 if (!mTexture
->HasAllocatedHandles()) {
2487 aContext
.RemoveSharedTexture(mTexture
);
2491 bool DrawTargetWebgl::SharedContext::RemoveStandaloneTexture(
2492 const RefPtr
<StandaloneTexture
>& aTexture
) {
2493 auto pos
= std::find(mStandaloneTextures
.begin(), mStandaloneTextures
.end(),
2495 if (pos
== mStandaloneTextures
.end()) {
2498 mTotalTextureMemory
-= aTexture
->UsedBytes();
2499 mStandaloneTextures
.erase(pos
);
2501 mWebgl
->DeleteTexture(aTexture
->GetWebGLTexture());
2505 void StandaloneTexture::Cleanup(DrawTargetWebgl::SharedContext
& aContext
) {
2506 aContext
.RemoveStandaloneTexture(this);
2509 // Prune a given texture handle and release its associated resources.
2510 void DrawTargetWebgl::SharedContext::PruneTextureHandle(
2511 const RefPtr
<TextureHandle
>& aHandle
) {
2512 // Invalidate the handle so nothing will subsequently use its contents.
2513 aHandle
->Invalidate();
2514 // If the handle has an associated SourceSurface, unlink it.
2515 UnlinkSurfaceTexture(aHandle
);
2516 // If the handle has an associated CacheEntry, unlink it.
2517 if (RefPtr
<CacheEntry
> entry
= aHandle
->GetCacheEntry()) {
2520 // Deduct the used space from the total.
2521 mUsedTextureMemory
-= aHandle
->UsedBytes();
2522 // Ensure any allocated shared or standalone texture regions get freed.
2523 aHandle
->Cleanup(*this);
2526 // Prune any texture memory above the limit (or margin below the limit) or any
2527 // least-recently-used handles that are no longer associated with any usable
2529 bool DrawTargetWebgl::SharedContext::PruneTextureMemory(size_t aMargin
,
2530 bool aPruneUnused
) {
2531 // The maximum amount of texture memory that may be used by textures.
2532 size_t maxBytes
= StaticPrefs::gfx_canvas_accelerated_cache_size() << 20;
2533 maxBytes
-= std::min(maxBytes
, aMargin
);
2534 size_t maxItems
= StaticPrefs::gfx_canvas_accelerated_cache_items();
2535 size_t oldItems
= mNumTextureHandles
;
2536 while (!mTextureHandles
.isEmpty() &&
2537 (mUsedTextureMemory
> maxBytes
|| mNumTextureHandles
> maxItems
||
2538 (aPruneUnused
&& !mTextureHandles
.getLast()->IsUsed()))) {
2539 PruneTextureHandle(mTextureHandles
.popLast());
2540 --mNumTextureHandles
;
2542 return mNumTextureHandles
< oldItems
;
2545 void DrawTargetWebgl::FillRect(const Rect
& aRect
, const Pattern
& aPattern
,
2546 const DrawOptions
& aOptions
) {
2547 if (SupportsPattern(aPattern
)) {
2548 if (RectInsidePrecisionLimits(aRect
, mTransform
)) {
2549 DrawRect(aRect
, aPattern
, aOptions
);
2552 if (aPattern
.GetType() == PatternType::COLOR
&&
2553 RectContainsViewport(aRect
)) {
2554 // If the pattern is transform-invariant and the rect encompasses the
2555 // entire viewport, just clip drawing to the viewport to avoid transform
2557 DrawRect(Rect(GetRect()), aPattern
, aOptions
, Nothing(), nullptr, false);
2562 MarkSkiaChanged(aOptions
);
2563 mSkia
->FillRect(aRect
, aPattern
, aOptions
);
2565 // If the pattern is unsupported, then transform the rect to a path so it
2568 skiaPath
.addRect(RectToSkRect(aRect
));
2569 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
2570 DrawPath(path
, aPattern
, aOptions
);
2574 void CacheEntry::Link(const RefPtr
<TextureHandle
>& aHandle
) {
2576 mHandle
->SetCacheEntry(this);
2579 // When the CacheEntry becomes unused, it marks the corresponding
2580 // TextureHandle as unused and unlinks it from the CacheEntry. The
2581 // entry is removed from its containing Cache, if applicable.
2582 void CacheEntry::Unlink() {
2583 // The entry may not have a valid handle if rasterization failed.
2585 mHandle
->SetCacheEntry(nullptr);
2592 // Hashes a path and pattern to a single hash value that can be used for quick
2593 // comparisons. This currently avoids to expensive hashing of internal path
2594 // and pattern data for speed, relying instead on later exact comparisons for
2596 HashNumber
PathCacheEntry::HashPath(const QuantizedPath
& aPath
,
2597 const Pattern
* aPattern
,
2598 const Matrix
& aTransform
,
2599 const IntRect
& aBounds
,
2600 const Point
& aOrigin
) {
2601 HashNumber hash
= 0;
2602 hash
= AddToHash(hash
, aPath
.mPath
.num_types
);
2603 hash
= AddToHash(hash
, aPath
.mPath
.num_points
);
2604 if (aPath
.mPath
.num_points
> 0) {
2605 hash
= AddToHash(hash
, aPath
.mPath
.points
[0].x
);
2606 hash
= AddToHash(hash
, aPath
.mPath
.points
[0].y
);
2607 if (aPath
.mPath
.num_points
> 1) {
2608 hash
= AddToHash(hash
, aPath
.mPath
.points
[1].x
);
2609 hash
= AddToHash(hash
, aPath
.mPath
.points
[1].y
);
2612 // Quantize the relative offset of the path to its bounds.
2613 IntPoint offset
= RoundedToInt((aOrigin
- Point(aBounds
.TopLeft())) * 16.0f
);
2614 hash
= AddToHash(hash
, offset
.x
);
2615 hash
= AddToHash(hash
, offset
.y
);
2616 hash
= AddToHash(hash
, aBounds
.width
);
2617 hash
= AddToHash(hash
, aBounds
.height
);
2619 hash
= AddToHash(hash
, (int)aPattern
->GetType());
2624 // When caching rendered geometry, we need to ensure the scale and orientation
2625 // is approximately the same. The offset will be considered separately.
2626 static inline bool HasMatchingScale(const Matrix
& aTransform1
,
2627 const Matrix
& aTransform2
) {
2628 return FuzzyEqual(aTransform1
._11
, aTransform2
._11
) &&
2629 FuzzyEqual(aTransform1
._12
, aTransform2
._12
) &&
2630 FuzzyEqual(aTransform1
._21
, aTransform2
._21
) &&
2631 FuzzyEqual(aTransform1
._22
, aTransform2
._22
);
2634 // Determines if an existing path cache entry matches an incoming path and
2636 inline bool PathCacheEntry::MatchesPath(const QuantizedPath
& aPath
,
2637 const Pattern
* aPattern
,
2638 const StrokeOptions
* aStrokeOptions
,
2639 const Matrix
& aTransform
,
2640 const IntRect
& aBounds
,
2641 const Point
& aOrigin
, HashNumber aHash
,
2643 return aHash
== mHash
&& HasMatchingScale(aTransform
, mTransform
) &&
2644 // Ensure the clipped relative bounds fit inside those of the entry
2645 aBounds
.x
- aOrigin
.x
>= mBounds
.x
- mOrigin
.x
&&
2646 (aBounds
.x
- aOrigin
.x
) + aBounds
.width
<=
2647 (mBounds
.x
- mOrigin
.x
) + mBounds
.width
&&
2648 aBounds
.y
- aOrigin
.y
>= mBounds
.y
- mOrigin
.y
&&
2649 (aBounds
.y
- aOrigin
.y
) + aBounds
.height
<=
2650 (mBounds
.y
- mOrigin
.y
) + mBounds
.height
&&
2652 (!aPattern
? !mPattern
: mPattern
&& *aPattern
== *mPattern
) &&
2655 : mStrokeOptions
&& *aStrokeOptions
== *mStrokeOptions
) &&
2659 PathCacheEntry::PathCacheEntry(QuantizedPath
&& aPath
, Pattern
* aPattern
,
2660 StoredStrokeOptions
* aStrokeOptions
,
2661 const Matrix
& aTransform
, const IntRect
& aBounds
,
2662 const Point
& aOrigin
, HashNumber aHash
,
2664 : CacheEntryImpl
<PathCacheEntry
>(aTransform
, aBounds
, aHash
),
2665 mPath(std::move(aPath
)),
2668 mStrokeOptions(aStrokeOptions
),
2671 // Attempt to find a matching entry in the path cache. If one isn't found,
2672 // a new entry will be created. The caller should check whether the contained
2673 // texture handle is valid to determine if it will need to render the text run
2674 // or just reuse the cached texture.
2675 already_AddRefed
<PathCacheEntry
> PathCache::FindOrInsertEntry(
2676 QuantizedPath aPath
, const Pattern
* aPattern
,
2677 const StrokeOptions
* aStrokeOptions
, const Matrix
& aTransform
,
2678 const IntRect
& aBounds
, const Point
& aOrigin
, float aSigma
) {
2680 PathCacheEntry::HashPath(aPath
, aPattern
, aTransform
, aBounds
, aOrigin
);
2681 for (const RefPtr
<PathCacheEntry
>& entry
: GetChain(hash
)) {
2682 if (entry
->MatchesPath(aPath
, aPattern
, aStrokeOptions
, aTransform
, aBounds
,
2683 aOrigin
, hash
, aSigma
)) {
2684 return do_AddRef(entry
);
2687 Pattern
* pattern
= nullptr;
2689 pattern
= aPattern
->CloneWeak();
2694 StoredStrokeOptions
* strokeOptions
= nullptr;
2695 if (aStrokeOptions
) {
2696 strokeOptions
= aStrokeOptions
->Clone();
2697 if (!strokeOptions
) {
2701 RefPtr
<PathCacheEntry
> entry
=
2702 new PathCacheEntry(std::move(aPath
), pattern
, strokeOptions
, aTransform
,
2703 aBounds
, aOrigin
, hash
, aSigma
);
2705 return entry
.forget();
2708 void DrawTargetWebgl::Fill(const Path
* aPath
, const Pattern
& aPattern
,
2709 const DrawOptions
& aOptions
) {
2710 if (!aPath
|| aPath
->GetBackendType() != BackendType::SKIA
) {
2714 const SkPath
& skiaPath
= static_cast<const PathSkia
*>(aPath
)->GetPath();
2715 SkRect skiaRect
= SkRect::MakeEmpty();
2716 // Draw the path as a simple rectangle with a supported pattern when possible.
2717 if (skiaPath
.isRect(&skiaRect
) && SupportsPattern(aPattern
)) {
2718 Rect rect
= SkRectToRect(skiaRect
);
2719 if (RectInsidePrecisionLimits(rect
, mTransform
)) {
2720 DrawRect(rect
, aPattern
, aOptions
);
2723 if (aPattern
.GetType() == PatternType::COLOR
&&
2724 RectContainsViewport(rect
)) {
2725 // If the pattern is transform-invariant and the rect encompasses the
2726 // entire viewport, just clip drawing to the viewport to avoid transform
2728 DrawRect(Rect(GetRect()), aPattern
, aOptions
, Nothing(), nullptr, false);
2733 DrawPath(aPath
, aPattern
, aOptions
);
2736 QuantizedPath::QuantizedPath(const WGR::Path
& aPath
) : mPath(aPath
) {}
2738 QuantizedPath::QuantizedPath(QuantizedPath
&& aPath
) noexcept
2739 : mPath(aPath
.mPath
) {
2740 aPath
.mPath
.points
= nullptr;
2741 aPath
.mPath
.num_points
= 0;
2742 aPath
.mPath
.types
= nullptr;
2743 aPath
.mPath
.num_types
= 0;
2746 QuantizedPath::~QuantizedPath() {
2747 if (mPath
.points
|| mPath
.types
) {
2748 WGR::wgr_path_release(mPath
);
2752 bool QuantizedPath::operator==(const QuantizedPath
& aOther
) const {
2753 return mPath
.num_types
== aOther
.mPath
.num_types
&&
2754 mPath
.num_points
== aOther
.mPath
.num_points
&&
2755 mPath
.fill_mode
== aOther
.mPath
.fill_mode
&&
2756 !memcmp(mPath
.types
, aOther
.mPath
.types
,
2757 mPath
.num_types
* sizeof(uint8_t)) &&
2758 !memcmp(mPath
.points
, aOther
.mPath
.points
,
2759 mPath
.num_points
* sizeof(WGR::Point
));
2762 // Generate a quantized path from the Skia path using WGR. The supplied
2763 // transform will be applied to the path. The path is stored relative to its
2764 // bounds origin to support translation later.
2765 static Maybe
<QuantizedPath
> GenerateQuantizedPath(const SkPath
& aPath
,
2766 const Rect
& aBounds
,
2767 const Matrix
& aTransform
) {
2768 WGR::PathBuilder
* pb
= WGR::wgr_new_builder();
2772 WGR::wgr_builder_set_fill_mode(pb
,
2773 aPath
.getFillType() == SkPathFillType::kWinding
2774 ? WGR::FillMode::Winding
2775 : WGR::FillMode::EvenOdd
);
2777 SkPath::RawIter
iter(aPath
);
2779 SkPath::Verb currentVerb
;
2781 // printf_stderr("bounds: (%d, %d) %d x %d\n", aBounds.x, aBounds.y,
2782 // aBounds.width, aBounds.height);
2783 Matrix transform
= aTransform
;
2784 transform
.PostTranslate(-aBounds
.TopLeft());
2785 while ((currentVerb
= iter
.next(params
)) != SkPath::kDone_Verb
) {
2786 switch (currentVerb
) {
2787 case SkPath::kMove_Verb
: {
2788 Point p0
= transform
.TransformPoint(SkPointToPoint(params
[0]));
2789 WGR::wgr_builder_move_to(pb
, p0
.x
, p0
.y
);
2792 case SkPath::kLine_Verb
: {
2793 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
2794 WGR::wgr_builder_line_to(pb
, p1
.x
, p1
.y
);
2797 case SkPath::kCubic_Verb
: {
2798 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
2799 Point p2
= transform
.TransformPoint(SkPointToPoint(params
[2]));
2800 Point p3
= transform
.TransformPoint(SkPointToPoint(params
[3]));
2801 // printf_stderr("cubic (%f, %f), (%f, %f), (%f, %f)\n", p1.x, p1.y,
2802 // p2.x, p2.y, p3.x, p3.y);
2803 WGR::wgr_builder_curve_to(pb
, p1
.x
, p1
.y
, p2
.x
, p2
.y
, p3
.x
, p3
.y
);
2806 case SkPath::kQuad_Verb
: {
2807 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
2808 Point p2
= transform
.TransformPoint(SkPointToPoint(params
[2]));
2809 // printf_stderr("quad (%f, %f), (%f, %f)\n", p1.x, p1.y, p2.x, p2.y);
2810 WGR::wgr_builder_quad_to(pb
, p1
.x
, p1
.y
, p2
.x
, p2
.y
);
2813 case SkPath::kConic_Verb
: {
2814 Point p0
= transform
.TransformPoint(SkPointToPoint(params
[0]));
2815 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
2816 Point p2
= transform
.TransformPoint(SkPointToPoint(params
[2]));
2817 float w
= iter
.conicWeight();
2818 std::vector
<Point
> quads
;
2819 int numQuads
= ConvertConicToQuads(p0
, p1
, p2
, w
, quads
);
2820 for (int i
= 0; i
< numQuads
; i
++) {
2821 Point q1
= quads
[2 * i
+ 1];
2822 Point q2
= quads
[2 * i
+ 2];
2823 // printf_stderr("conic quad (%f, %f), (%f, %f)\n", q1.x, q1.y, q2.x,
2825 WGR::wgr_builder_quad_to(pb
, q1
.x
, q1
.y
, q2
.x
, q2
.y
);
2829 case SkPath::kClose_Verb
:
2830 // printf_stderr("close\n");
2831 WGR::wgr_builder_close(pb
);
2835 // Unexpected verb found in path!
2836 WGR::wgr_builder_release(pb
);
2841 WGR::Path p
= WGR::wgr_builder_get_path(pb
);
2842 WGR::wgr_builder_release(pb
);
2843 if (!p
.num_points
|| !p
.num_types
) {
2844 WGR::wgr_path_release(p
);
2847 return Some(QuantizedPath(p
));
2850 // Get the output vertex buffer using WGR from an input quantized path.
2851 static Maybe
<WGR::VertexBuffer
> GeneratePathVertexBuffer(
2852 const QuantizedPath
& aPath
, const IntRect
& aClipRect
,
2853 bool aRasterizationTruncates
, WGR::OutputVertex
* aBuffer
,
2854 size_t aBufferCapacity
) {
2855 WGR::VertexBuffer vb
= WGR::wgr_path_rasterize_to_tri_list(
2856 &aPath
.mPath
, aClipRect
.x
, aClipRect
.y
, aClipRect
.width
, aClipRect
.height
,
2857 true, false, aRasterizationTruncates
, aBuffer
, aBufferCapacity
);
2858 if (!vb
.len
|| (aBuffer
&& vb
.len
> aBufferCapacity
)) {
2859 WGR::wgr_vertex_buffer_release(vb
);
2865 static inline AAStroke::LineJoin
ToAAStrokeLineJoin(JoinStyle aJoin
) {
2867 case JoinStyle::BEVEL
:
2868 return AAStroke::LineJoin::Bevel
;
2869 case JoinStyle::ROUND
:
2870 return AAStroke::LineJoin::Round
;
2871 case JoinStyle::MITER
:
2872 case JoinStyle::MITER_OR_BEVEL
:
2873 return AAStroke::LineJoin::Miter
;
2875 return AAStroke::LineJoin::Miter
;
2878 static inline AAStroke::LineCap
ToAAStrokeLineCap(CapStyle aCap
) {
2880 case CapStyle::BUTT
:
2881 return AAStroke::LineCap::Butt
;
2882 case CapStyle::ROUND
:
2883 return AAStroke::LineCap::Round
;
2884 case CapStyle::SQUARE
:
2885 return AAStroke::LineCap::Square
;
2887 return AAStroke::LineCap::Butt
;
2890 static inline Point
WGRPointToPoint(const WGR::Point
& aPoint
) {
2891 return Point(IntPoint(aPoint
.x
, aPoint
.y
)) * (1.0f
/ 16.0f
);
2894 // Generates a vertex buffer for a stroked path using aa-stroke.
2895 static Maybe
<AAStroke::VertexBuffer
> GenerateStrokeVertexBuffer(
2896 const QuantizedPath
& aPath
, const StrokeOptions
* aStrokeOptions
,
2897 float aScale
, WGR::OutputVertex
* aBuffer
, size_t aBufferCapacity
) {
2898 AAStroke::StrokeStyle style
= {aStrokeOptions
->mLineWidth
* aScale
,
2899 ToAAStrokeLineCap(aStrokeOptions
->mLineCap
),
2900 ToAAStrokeLineJoin(aStrokeOptions
->mLineJoin
),
2901 aStrokeOptions
->mMiterLimit
};
2902 if (style
.width
<= 0.0f
|| !std::isfinite(style
.width
) ||
2903 style
.miter_limit
<= 0.0f
|| !std::isfinite(style
.miter_limit
)) {
2906 AAStroke::Stroker
* s
= AAStroke::aa_stroke_new(
2907 &style
, (AAStroke::OutputVertex
*)aBuffer
, aBufferCapacity
);
2909 size_t curPoint
= 0;
2910 for (size_t curType
= 0; valid
&& curType
< aPath
.mPath
.num_types
;) {
2911 // Verify that we are at the start of a sub-path.
2912 if ((aPath
.mPath
.types
[curType
] & WGR::PathPointTypePathTypeMask
) !=
2913 WGR::PathPointTypeStart
) {
2917 // Find where the next sub-path starts so we can locate the end.
2918 size_t endType
= curType
+ 1;
2919 for (; endType
< aPath
.mPath
.num_types
; endType
++) {
2920 if ((aPath
.mPath
.types
[endType
] & WGR::PathPointTypePathTypeMask
) ==
2921 WGR::PathPointTypeStart
) {
2925 // Check if the path is closed. This is a flag modifying the last type.
2927 (aPath
.mPath
.types
[endType
- 1] & WGR::PathPointTypeCloseSubpath
) != 0;
2928 for (; curType
< endType
; curType
++) {
2929 // If this is the last type and the sub-path is not closed, determine if
2930 // this segment should be capped.
2931 bool end
= curType
+ 1 == endType
&& !closed
;
2932 switch (aPath
.mPath
.types
[curType
] & WGR::PathPointTypePathTypeMask
) {
2933 case WGR::PathPointTypeStart
: {
2934 if (curPoint
+ 1 > aPath
.mPath
.num_points
) {
2938 Point p1
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
]);
2939 AAStroke::aa_stroke_move_to(s
, p1
.x
, p1
.y
, closed
);
2941 AAStroke::aa_stroke_line_to(s
, p1
.x
, p1
.y
, true);
2946 case WGR::PathPointTypeLine
: {
2947 if (curPoint
+ 1 > aPath
.mPath
.num_points
) {
2951 Point p1
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
]);
2952 AAStroke::aa_stroke_line_to(s
, p1
.x
, p1
.y
, end
);
2956 case WGR::PathPointTypeBezier
: {
2957 if (curPoint
+ 3 > aPath
.mPath
.num_points
) {
2961 Point p1
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
]);
2962 Point p2
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
+ 1]);
2963 Point p3
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
+ 2]);
2964 AAStroke::aa_stroke_curve_to(s
, p1
.x
, p1
.y
, p2
.x
, p2
.y
, p3
.x
, p3
.y
,
2970 MOZ_ASSERT(false, "Unknown WGR path point type");
2975 // Close the sub-path if necessary.
2976 if (valid
&& closed
) {
2977 AAStroke::aa_stroke_close(s
);
2980 Maybe
<AAStroke::VertexBuffer
> result
;
2982 AAStroke::VertexBuffer vb
= AAStroke::aa_stroke_finish(s
);
2983 if (!vb
.len
|| (aBuffer
&& vb
.len
> aBufferCapacity
)) {
2984 AAStroke::aa_stroke_vertex_buffer_release(vb
);
2989 AAStroke::aa_stroke_release(s
);
2993 // Search the path cache for any entries stored in the path vertex buffer and
2995 void PathCache::ClearVertexRanges() {
2996 for (auto& chain
: mChains
) {
2997 PathCacheEntry
* entry
= chain
.getFirst();
2999 PathCacheEntry
* next
= entry
->getNext();
3000 if (entry
->GetVertexRange().IsValid()) {
3008 inline bool DrawTargetWebgl::ShouldAccelPath(
3009 const DrawOptions
& aOptions
, const StrokeOptions
* aStrokeOptions
) {
3010 return mWebglValid
&& SupportsDrawOptions(aOptions
) && PrepareContext();
3013 enum class AAStrokeMode
{
3019 // For now, we only directly support stroking solid color patterns to limit
3020 // artifacts from blending of overlapping geometry generated by AAStroke. Other
3021 // types of patterns may be partially supported by rendering to a temporary
3023 static inline AAStrokeMode
SupportsAAStroke(const Pattern
& aPattern
,
3024 const DrawOptions
& aOptions
,
3025 const StrokeOptions
& aStrokeOptions
,
3026 bool aAllowStrokeAlpha
) {
3027 if (aStrokeOptions
.mDashPattern
) {
3028 return AAStrokeMode::Unsupported
;
3030 switch (aOptions
.mCompositionOp
) {
3031 case CompositionOp::OP_SOURCE
:
3032 return AAStrokeMode::Geometry
;
3033 case CompositionOp::OP_OVER
:
3034 if (aPattern
.GetType() == PatternType::COLOR
) {
3035 return static_cast<const ColorPattern
&>(aPattern
).mColor
.a
*
3039 ? AAStrokeMode::Mask
3040 : AAStrokeMode::Geometry
;
3042 return AAStrokeMode::Unsupported
;
3044 return AAStrokeMode::Unsupported
;
3048 // Render an AA-Stroke'd vertex range into an R8 mask texture for subsequent
3050 already_AddRefed
<TextureHandle
> DrawTargetWebgl::SharedContext::DrawStrokeMask(
3051 const PathVertexRange
& aVertexRange
, const IntSize
& aSize
) {
3052 // Allocate a new texture handle to store the rendered mask.
3053 RefPtr
<TextureHandle
> handle
=
3054 AllocateTextureHandle(SurfaceFormat::A8
, aSize
, true, true);
3059 IntRect texBounds
= handle
->GetBounds();
3060 BackingTexture
* backing
= handle
->GetBackingTexture();
3061 if (!backing
->IsInitialized()) {
3062 // If the backing texture is uninitialized, it needs its sampling parameters
3063 // set for later use.
3064 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, backing
->GetWebGLTexture());
3065 mWebgl
->TexStorage2D(LOCAL_GL_TEXTURE_2D
, 1, LOCAL_GL_R8
,
3066 backing
->GetSize().width
, backing
->GetSize().height
);
3067 InitTexParameters(backing
->GetWebGLTexture());
3071 // Set up a scratch framebuffer to render to the appropriate sub-texture of
3072 // the backing texture.
3073 if (!mScratchFramebuffer
) {
3074 mScratchFramebuffer
= mWebgl
->CreateFramebuffer();
3076 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mScratchFramebuffer
);
3077 mWebgl
->FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER
, LOCAL_GL_COLOR_ATTACHMENT0
,
3078 LOCAL_GL_TEXTURE_2D
, backing
->GetWebGLTexture(),
3080 mWebgl
->Viewport(texBounds
.x
, texBounds
.y
, texBounds
.width
, texBounds
.height
);
3081 if (!backing
->IsInitialized()) {
3082 backing
->MarkInitialized();
3083 // If the backing texture is uninitialized, then clear the entire backing
3084 // texture to initialize it.
3087 // Clear only the sub-texture.
3088 EnableScissor(texBounds
);
3090 // Ensure the mask background is clear.
3091 mWebgl
->ClearColor(0.0f
, 0.0f
, 0.0f
, 0.0f
);
3092 mWebgl
->Clear(LOCAL_GL_COLOR_BUFFER_BIT
);
3094 // Reset any blending when drawing the mask.
3095 SetBlendState(CompositionOp::OP_OVER
);
3097 // Set up the solid color shader to draw a simple opaque mask.
3098 if (mLastProgram
!= mSolidProgram
) {
3099 mWebgl
->UseProgram(mSolidProgram
);
3100 mLastProgram
= mSolidProgram
;
3102 Array
<float, 2> viewportData
= {float(texBounds
.width
),
3103 float(texBounds
.height
)};
3104 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramViewport
, viewportData
,
3105 mSolidProgramUniformState
.mViewport
);
3106 Array
<float, 1> aaData
= {0.0f
};
3107 MaybeUniformData(LOCAL_GL_FLOAT
, mSolidProgramAA
, aaData
,
3108 mSolidProgramUniformState
.mAA
);
3109 Array
<float, 4> clipData
= {-0.5f
, -0.5f
, float(texBounds
.width
) + 0.5f
,
3110 float(texBounds
.height
) + 0.5f
};
3111 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramClipBounds
, clipData
,
3112 mSolidProgramUniformState
.mClipBounds
);
3113 Array
<float, 4> colorData
= {1.0f
, 1.0f
, 1.0f
, 1.0f
};
3114 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramColor
, colorData
,
3115 mSolidProgramUniformState
.mColor
);
3116 Array
<float, 6> xformData
= {1.0f
, 0.0f
, 0.0f
, 1.0f
, 0.0f
, 0.0f
};
3117 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramTransform
, xformData
,
3118 mSolidProgramUniformState
.mTransform
);
3120 // Ensure the current clip mask is ignored.
3121 RefPtr
<WebGLTextureJS
> prevClipMask
= mLastClipMask
;
3124 // Draw the mask using the supplied path vertex range.
3125 mWebgl
->DrawArrays(LOCAL_GL_TRIANGLES
, GLint(aVertexRange
.mOffset
),
3126 GLsizei(aVertexRange
.mLength
));
3128 // Restore the previous framebuffer state.
3129 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mCurrentTarget
->mFramebuffer
);
3130 mWebgl
->Viewport(0, 0, mViewportSize
.width
, mViewportSize
.height
);
3132 SetClipMask(prevClipMask
);
3135 return handle
.forget();
3138 bool DrawTargetWebgl::SharedContext::DrawPathAccel(
3139 const Path
* aPath
, const Pattern
& aPattern
, const DrawOptions
& aOptions
,
3140 const StrokeOptions
* aStrokeOptions
, bool aAllowStrokeAlpha
,
3141 const ShadowOptions
* aShadow
, bool aCacheable
) {
3142 // Get the transformed bounds for the path and conservatively check if the
3143 // bounds overlap the canvas.
3144 const PathSkia
* pathSkia
= static_cast<const PathSkia
*>(aPath
);
3145 const Matrix
& currentTransform
= GetTransform();
3146 Rect bounds
= pathSkia
->GetFastBounds(currentTransform
, aStrokeOptions
);
3147 // If the path is empty, then there is nothing to draw.
3148 if (bounds
.IsEmpty()) {
3151 IntRect
viewport(IntPoint(), mViewportSize
);
3153 // Inflate the bounds to account for the blur radius.
3154 bounds
+= aShadow
->mOffset
;
3155 int32_t blurRadius
= aShadow
->BlurRadius();
3156 bounds
.Inflate(blurRadius
);
3157 viewport
.Inflate(blurRadius
);
3159 Point realOrigin
= bounds
.TopLeft();
3161 // Quantize the path origin to increase the reuse of cache entries.
3164 bounds
.Scale(0.25f
);
3166 Point quantizedOrigin
= bounds
.TopLeft();
3167 // If the path doesn't intersect the viewport, then there is nothing to draw.
3168 IntRect intBounds
= RoundedOut(bounds
).Intersect(viewport
);
3169 if (intBounds
.IsEmpty()) {
3172 // Nudge the bounds to account for the quantization rounding.
3173 Rect quantBounds
= Rect(intBounds
) + (realOrigin
- quantizedOrigin
);
3174 // If the pattern is a solid color, then this will be used along with a path
3175 // mask to render the path, as opposed to baking the pattern into the cached
3177 Maybe
<DeviceColor
> color
=
3178 aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
3179 ? Some(DeviceColor(1, 1, 1, 1))
3180 : (aPattern
.GetType() == PatternType::COLOR
3181 ? Some(static_cast<const ColorPattern
&>(aPattern
).mColor
)
3183 // Look for an existing path cache entry, if possible, or otherwise create
3184 // one. If the draw request is not cacheable, then don't create an entry.
3185 RefPtr
<PathCacheEntry
> entry
;
3186 RefPtr
<TextureHandle
> handle
;
3189 mPathCache
= MakeUnique
<PathCache
>();
3191 // Use a quantized, relative (to its bounds origin) version of the path as
3192 // a cache key to help limit cache bloat.
3193 Maybe
<QuantizedPath
> qp
= GenerateQuantizedPath(
3194 pathSkia
->GetPath(), quantBounds
, currentTransform
);
3198 entry
= mPathCache
->FindOrInsertEntry(
3199 std::move(*qp
), color
? nullptr : &aPattern
, aStrokeOptions
,
3200 currentTransform
, intBounds
, quantizedOrigin
,
3201 aShadow
? aShadow
->mSigma
: -1.0f
);
3205 handle
= entry
->GetHandle();
3208 // If there is a shadow, it needs to draw with the shadow color rather than
3210 Maybe
<DeviceColor
> shadowColor
= color
;
3211 if (aShadow
&& aOptions
.mCompositionOp
!= CompositionOp::OP_CLEAR
) {
3212 shadowColor
= Some(aShadow
->mColor
);
3214 shadowColor
->a
*= color
->a
;
3217 SamplingFilter filter
=
3218 aShadow
? SamplingFilter::GOOD
: GetSamplingFilter(aPattern
);
3219 if (handle
&& handle
->IsValid()) {
3220 // If the entry has a valid texture handle still, use it. However, the
3221 // entry texture is assumed to be located relative to its previous bounds.
3222 // We need to offset the pattern by the difference between its new unclipped
3223 // origin and its previous previous unclipped origin. Then when we finally
3224 // draw a rectangle at the expected new bounds, it will overlap the portion
3225 // of the old entry texture we actually need to sample from.
3227 (realOrigin
- entry
->GetOrigin()) + entry
->GetBounds().TopLeft();
3228 SurfacePattern
pathPattern(nullptr, ExtendMode::CLAMP
,
3229 Matrix::Translation(offset
), filter
);
3230 return DrawRectAccel(quantBounds
, pathPattern
, aOptions
, shadowColor
,
3231 &handle
, false, true, true);
3234 if (mPathVertexCapacity
> 0 && !handle
&& entry
&& !aShadow
&&
3235 aOptions
.mAntialiasMode
!= AntialiasMode::NONE
&&
3236 SupportsPattern(aPattern
) &&
3237 entry
->GetPath().mPath
.num_types
<= mPathMaxComplexity
) {
3238 if (entry
->GetVertexRange().IsValid()) {
3239 // If there is a valid cached vertex data in the path vertex buffer, then
3240 // just draw that. We must draw at integer pixel boundaries (using
3241 // intBounds instead of quantBounds) due to WGR's reliance on pixel center
3243 mCurrentTarget
->mProfile
.OnCacheHit();
3244 return DrawRectAccel(Rect(intBounds
.TopLeft(), Size(1, 1)), aPattern
,
3245 aOptions
, Nothing(), nullptr, false, true, true,
3246 false, nullptr, &entry
->GetVertexRange());
3249 // printf_stderr("Generating... verbs %d, points %d\n",
3250 // int(pathSkia->GetPath().countVerbs()),
3251 // int(pathSkia->GetPath().countPoints()));
3252 WGR::OutputVertex
* outputBuffer
= nullptr;
3253 size_t outputBufferCapacity
= 0;
3254 if (mWGROutputBuffer
) {
3255 outputBuffer
= mWGROutputBuffer
.get();
3256 outputBufferCapacity
= mPathVertexCapacity
/ sizeof(WGR::OutputVertex
);
3258 Maybe
<WGR::VertexBuffer
> wgrVB
;
3259 Maybe
<AAStroke::VertexBuffer
> strokeVB
;
3260 if (!aStrokeOptions
) {
3261 wgrVB
= GeneratePathVertexBuffer(
3262 entry
->GetPath(), IntRect(-intBounds
.TopLeft(), mViewportSize
),
3263 mRasterizationTruncates
, outputBuffer
, outputBufferCapacity
);
3265 if (mPathAAStroke
&&
3266 SupportsAAStroke(aPattern
, aOptions
, *aStrokeOptions
,
3267 aAllowStrokeAlpha
) != AAStrokeMode::Unsupported
) {
3268 auto scaleFactors
= currentTransform
.ScaleFactors();
3269 if (scaleFactors
.AreScalesSame()) {
3270 strokeVB
= GenerateStrokeVertexBuffer(
3271 entry
->GetPath(), aStrokeOptions
, scaleFactors
.xScale
,
3272 outputBuffer
, outputBufferCapacity
);
3275 if (!strokeVB
&& mPathWGRStroke
) {
3276 // If stroking, then generate a path to fill the stroked region. This
3277 // path will need to be quantized again because it differs from the
3278 // path used for the cache entry, but this allows us to avoid
3279 // generating a fill path on a cache hit.
3280 Maybe
<Rect
> cullRect
;
3281 Matrix invTransform
= currentTransform
;
3282 if (invTransform
.Invert()) {
3283 // Transform the stroking clip rect from device space to local
3285 Rect invRect
= invTransform
.TransformBounds(Rect(mClipRect
));
3287 cullRect
= Some(invRect
);
3290 if (pathSkia
->GetFillPath(*aStrokeOptions
, currentTransform
, fillPath
,
3292 // printf_stderr(" stroke fill... verbs %d, points %d\n",
3293 // int(fillPath.countVerbs()),
3294 // int(fillPath.countPoints()));
3295 if (Maybe
<QuantizedPath
> qp
= GenerateQuantizedPath(
3296 fillPath
, quantBounds
, currentTransform
)) {
3297 wgrVB
= GeneratePathVertexBuffer(
3298 *qp
, IntRect(-intBounds
.TopLeft(), mViewportSize
),
3299 mRasterizationTruncates
, outputBuffer
, outputBufferCapacity
);
3304 if (wgrVB
|| strokeVB
) {
3305 const uint8_t* vbData
=
3306 wgrVB
? (const uint8_t*)wgrVB
->data
: (const uint8_t*)strokeVB
->data
;
3307 if (outputBuffer
&& !vbData
) {
3308 vbData
= (const uint8_t*)outputBuffer
;
3310 size_t vbLen
= wgrVB
? wgrVB
->len
: strokeVB
->len
;
3311 uint32_t vertexBytes
= uint32_t(
3312 std::min(vbLen
* sizeof(WGR::OutputVertex
), size_t(UINT32_MAX
)));
3313 // printf_stderr(" ... %d verts, %d bytes\n", int(vbLen),
3314 // int(vertexBytes));
3315 if (vertexBytes
> mPathVertexCapacity
- mPathVertexOffset
&&
3316 vertexBytes
<= mPathVertexCapacity
- sizeof(kRectVertexData
)) {
3317 // If the vertex data is too large to fit in the remaining path vertex
3318 // buffer, then orphan the contents of the vertex buffer to make room
3321 mPathCache
->ClearVertexRanges();
3323 ResetPathVertexBuffer(false);
3325 if (vertexBytes
<= mPathVertexCapacity
- mPathVertexOffset
) {
3326 // If there is actually room to fit the vertex data in the vertex buffer
3327 // after orphaning as necessary, then upload the data to the next
3328 // available offset in the buffer.
3329 PathVertexRange
vertexRange(
3330 uint32_t(mPathVertexOffset
/ sizeof(WGR::OutputVertex
)),
3332 // printf_stderr(" ... offset %d\n", mPathVertexOffset);
3333 // Normal glBufferSubData interleaved with draw calls causes performance
3334 // issues on Mali, so use our special unsynchronized version. This is
3335 // safe as we never update regions referenced by pending draw calls.
3336 mWebgl
->RawBufferSubData(LOCAL_GL_ARRAY_BUFFER
, mPathVertexOffset
,
3337 vbData
, vertexBytes
,
3338 /* unsynchronized */ true);
3339 mPathVertexOffset
+= vertexBytes
;
3341 WGR::wgr_vertex_buffer_release(wgrVB
.ref());
3343 AAStroke::aa_stroke_vertex_buffer_release(strokeVB
.ref());
3346 SupportsAAStroke(aPattern
, aOptions
, *aStrokeOptions
,
3347 aAllowStrokeAlpha
) == AAStrokeMode::Mask
) {
3348 // Attempt to generate a stroke mask for path.
3349 if (RefPtr
<TextureHandle
> handle
=
3350 DrawStrokeMask(vertexRange
, intBounds
.Size())) {
3351 // Finally, draw the rendered stroke mask.
3353 entry
->Link(handle
);
3355 mCurrentTarget
->mProfile
.OnCacheMiss();
3356 SurfacePattern
maskPattern(
3357 nullptr, ExtendMode::CLAMP
,
3358 Matrix::Translation(quantBounds
.TopLeft()),
3359 SamplingFilter::GOOD
);
3360 return DrawRectAccel(quantBounds
, maskPattern
, aOptions
, color
,
3361 &handle
, false, true, true);
3364 // Remember the vertex range in the cache entry so that it can be
3367 entry
->SetVertexRange(vertexRange
);
3370 // Finally, draw the uploaded vertex data.
3371 mCurrentTarget
->mProfile
.OnCacheMiss();
3372 return DrawRectAccel(Rect(intBounds
.TopLeft(), Size(1, 1)), aPattern
,
3373 aOptions
, Nothing(), nullptr, false, true, true,
3374 false, nullptr, &vertexRange
);
3378 WGR::wgr_vertex_buffer_release(wgrVB
.ref());
3380 AAStroke::aa_stroke_vertex_buffer_release(strokeVB
.ref());
3383 // If we failed to draw the vertex data for some reason, then fall through
3384 // to the texture rasterization path.
3388 // If a stroke path covers too much screen area, it is likely that most is
3389 // empty space in the interior. This usually imposes too high a cost versus
3390 // just rasterizing without acceleration. Note that AA-Stroke generally
3391 // produces more acceptable amounts of geometry for larger paths, so we do
3392 // this heuristic after we attempt AA-Stroke.
3393 if (aStrokeOptions
&&
3394 intBounds
.width
* intBounds
.height
>
3395 (mViewportSize
.width
/ 2) * (mViewportSize
.height
/ 2)) {
3399 // If there isn't a valid texture handle, then we need to rasterize the
3400 // path in a software canvas and upload this to a texture. Solid color
3401 // patterns will be rendered as a path mask that can then be modulated
3402 // with any color. Other pattern types have to rasterize the pattern
3403 // directly into the cached texture.
3405 RefPtr
<DrawTargetSkia
> pathDT
= new DrawTargetSkia
;
3406 if (pathDT
->Init(intBounds
.Size(), color
|| aShadow
3408 : SurfaceFormat::B8G8R8A8
)) {
3409 Point offset
= -quantBounds
.TopLeft();
3411 // Ensure the the shadow is drawn at the requested offset
3412 offset
+= aShadow
->mOffset
;
3414 pathDT
->SetTransform(currentTransform
* Matrix::Translation(offset
));
3415 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
,
3416 aOptions
.mAntialiasMode
);
3417 static const ColorPattern
maskPattern(DeviceColor(1.0f
, 1.0f
, 1.0f
, 1.0f
));
3418 const Pattern
& cachePattern
= color
? maskPattern
: aPattern
;
3419 // If the source pattern is a DrawTargetWebgl snapshot, we may shift
3420 // targets when drawing the path, so back up the old target.
3421 DrawTargetWebgl
* oldTarget
= mCurrentTarget
;
3422 if (aStrokeOptions
) {
3423 pathDT
->Stroke(aPath
, cachePattern
, *aStrokeOptions
, drawOptions
);
3425 pathDT
->Fill(aPath
, cachePattern
, drawOptions
);
3427 if (aShadow
&& aShadow
->mSigma
> 0.0f
) {
3428 // Blur the shadow if required.
3429 uint8_t* data
= nullptr;
3432 SurfaceFormat format
= SurfaceFormat::UNKNOWN
;
3433 if (pathDT
->LockBits(&data
, &size
, &stride
, &format
)) {
3434 AlphaBoxBlur
blur(Rect(pathDT
->GetRect()), stride
, aShadow
->mSigma
,
3437 pathDT
->ReleaseBits(data
);
3440 RefPtr
<SourceSurface
> pathSurface
= pathDT
->Snapshot();
3442 // If the target changed, try to restore it.
3443 if (mCurrentTarget
!= oldTarget
&& !oldTarget
->PrepareContext()) {
3446 SurfacePattern
pathPattern(pathSurface
, ExtendMode::CLAMP
,
3447 Matrix::Translation(quantBounds
.TopLeft()),
3449 // Try and upload the rasterized path to a texture. If there is a
3450 // valid texture handle after this, then link it to the entry.
3451 // Otherwise, we might have to fall back to software drawing the
3452 // path, so unlink it from the entry.
3453 if (DrawRectAccel(quantBounds
, pathPattern
, aOptions
, shadowColor
,
3454 &handle
, false, true) &&
3457 entry
->Link(handle
);
3469 void DrawTargetWebgl::DrawPath(const Path
* aPath
, const Pattern
& aPattern
,
3470 const DrawOptions
& aOptions
,
3471 const StrokeOptions
* aStrokeOptions
,
3472 bool aAllowStrokeAlpha
) {
3473 // If there is a WebGL context, then try to cache the path to avoid slow
3475 if (ShouldAccelPath(aOptions
, aStrokeOptions
) &&
3476 mSharedContext
->DrawPathAccel(aPath
, aPattern
, aOptions
, aStrokeOptions
,
3477 aAllowStrokeAlpha
)) {
3481 // There was no path cache entry available to use, so fall back to drawing the
3483 MarkSkiaChanged(aOptions
);
3484 if (aStrokeOptions
) {
3485 mSkia
->Stroke(aPath
, aPattern
, *aStrokeOptions
, aOptions
);
3487 mSkia
->Fill(aPath
, aPattern
, aOptions
);
3491 void DrawTargetWebgl::DrawSurface(SourceSurface
* aSurface
, const Rect
& aDest
,
3492 const Rect
& aSource
,
3493 const DrawSurfaceOptions
& aSurfOptions
,
3494 const DrawOptions
& aOptions
) {
3495 Matrix matrix
= Matrix::Scaling(aDest
.width
/ aSource
.width
,
3496 aDest
.height
/ aSource
.height
);
3497 matrix
.PreTranslate(-aSource
.x
, -aSource
.y
);
3498 matrix
.PostTranslate(aDest
.x
, aDest
.y
);
3499 SurfacePattern
pattern(aSurface
, ExtendMode::CLAMP
, matrix
,
3500 aSurfOptions
.mSamplingFilter
);
3501 DrawRect(aDest
, pattern
, aOptions
);
3504 void DrawTargetWebgl::Mask(const Pattern
& aSource
, const Pattern
& aMask
,
3505 const DrawOptions
& aOptions
) {
3506 if (!SupportsDrawOptions(aOptions
) ||
3507 aMask
.GetType() != PatternType::SURFACE
||
3508 aSource
.GetType() != PatternType::COLOR
) {
3509 MarkSkiaChanged(aOptions
);
3510 mSkia
->Mask(aSource
, aMask
, aOptions
);
3513 auto sourceColor
= static_cast<const ColorPattern
&>(aSource
).mColor
;
3514 auto maskPattern
= static_cast<const SurfacePattern
&>(aMask
);
3515 DrawRect(Rect(IntRect(IntPoint(), maskPattern
.mSurface
->GetSize())),
3516 maskPattern
, aOptions
, Some(sourceColor
));
3519 void DrawTargetWebgl::MaskSurface(const Pattern
& aSource
, SourceSurface
* aMask
,
3520 Point aOffset
, const DrawOptions
& aOptions
) {
3521 if (!SupportsDrawOptions(aOptions
) ||
3522 aSource
.GetType() != PatternType::COLOR
) {
3523 MarkSkiaChanged(aOptions
);
3524 mSkia
->MaskSurface(aSource
, aMask
, aOffset
, aOptions
);
3526 auto sourceColor
= static_cast<const ColorPattern
&>(aSource
).mColor
;
3527 SurfacePattern
pattern(aMask
, ExtendMode::CLAMP
,
3528 Matrix::Translation(aOffset
));
3529 DrawRect(Rect(aOffset
, Size(aMask
->GetSize())), pattern
, aOptions
,
3534 // Extract the surface's alpha values into an A8 surface.
3535 static already_AddRefed
<DataSourceSurface
> ExtractAlpha(SourceSurface
* aSurface
,
3536 bool aAllowSubpixelAA
) {
3537 RefPtr
<DataSourceSurface
> surfaceData
= aSurface
->GetDataSurface();
3541 DataSourceSurface::ScopedMap
srcMap(surfaceData
, DataSourceSurface::READ
);
3542 if (!srcMap
.IsMapped()) {
3545 IntSize size
= surfaceData
->GetSize();
3546 RefPtr
<DataSourceSurface
> alpha
=
3547 Factory::CreateDataSourceSurface(size
, SurfaceFormat::A8
, false);
3551 DataSourceSurface::ScopedMap
dstMap(alpha
, DataSourceSurface::WRITE
);
3552 if (!dstMap
.IsMapped()) {
3555 // For subpixel masks, ignore the alpha and instead sample one of the color
3556 // channels as if they were alpha.
3558 srcMap
.GetData(), srcMap
.GetStride(),
3559 aAllowSubpixelAA
? SurfaceFormat::A8R8G8B8
: surfaceData
->GetFormat(),
3560 dstMap
.GetData(), dstMap
.GetStride(), SurfaceFormat::A8
, size
);
3561 return alpha
.forget();
3564 void DrawTargetWebgl::DrawShadow(const Path
* aPath
, const Pattern
& aPattern
,
3565 const ShadowOptions
& aShadow
,
3566 const DrawOptions
& aOptions
,
3567 const StrokeOptions
* aStrokeOptions
) {
3568 // If there is a WebGL context, then try to cache the path to avoid slow
3570 if (ShouldAccelPath(aOptions
, aStrokeOptions
) &&
3571 mSharedContext
->DrawPathAccel(aPath
, aPattern
, aOptions
, aStrokeOptions
,
3576 // There was no path cache entry available to use, so fall back to drawing the
3578 MarkSkiaChanged(aOptions
);
3579 mSkia
->DrawShadow(aPath
, aPattern
, aShadow
, aOptions
, aStrokeOptions
);
3582 void DrawTargetWebgl::DrawSurfaceWithShadow(SourceSurface
* aSurface
,
3584 const ShadowOptions
& aShadow
,
3585 CompositionOp aOperator
) {
3586 DrawOptions
options(1.0f
, aOperator
);
3587 if (ShouldAccelPath(options
, nullptr)) {
3588 SurfacePattern
pattern(aSurface
, ExtendMode::CLAMP
,
3589 Matrix::Translation(aDest
));
3591 skiaPath
.addRect(RectToSkRect(Rect(aSurface
->GetRect()) + aDest
));
3592 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
3593 AutoRestoreTransform
restore(this);
3594 SetTransform(Matrix());
3595 if (mSharedContext
->DrawPathAccel(path
, pattern
, options
, nullptr, false,
3597 DrawRect(Rect(aSurface
->GetRect()) + aDest
, pattern
, options
);
3602 MarkSkiaChanged(options
);
3603 mSkia
->DrawSurfaceWithShadow(aSurface
, aDest
, aShadow
, aOperator
);
3606 already_AddRefed
<PathBuilder
> DrawTargetWebgl::CreatePathBuilder(
3607 FillRule aFillRule
) const {
3608 return mSkia
->CreatePathBuilder(aFillRule
);
3611 void DrawTargetWebgl::SetTransform(const Matrix
& aTransform
) {
3612 DrawTarget::SetTransform(aTransform
);
3613 mSkia
->SetTransform(aTransform
);
3616 void DrawTargetWebgl::StrokeRect(const Rect
& aRect
, const Pattern
& aPattern
,
3617 const StrokeOptions
& aStrokeOptions
,
3618 const DrawOptions
& aOptions
) {
3620 MarkSkiaChanged(aOptions
);
3621 mSkia
->StrokeRect(aRect
, aPattern
, aStrokeOptions
, aOptions
);
3623 // If the stroke options are unsupported, then transform the rect to a path
3624 // so it can be cached.
3626 skiaPath
.addRect(RectToSkRect(aRect
));
3627 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
3628 DrawPath(path
, aPattern
, aOptions
, &aStrokeOptions
, true);
3632 static inline bool IsThinLine(const Matrix
& aTransform
,
3633 const StrokeOptions
& aStrokeOptions
) {
3634 auto scale
= aTransform
.ScaleFactors();
3635 return std::max(scale
.xScale
, scale
.yScale
) * aStrokeOptions
.mLineWidth
<= 1;
3638 bool DrawTargetWebgl::StrokeLineAccel(const Point
& aStart
, const Point
& aEnd
,
3639 const Pattern
& aPattern
,
3640 const StrokeOptions
& aStrokeOptions
,
3641 const DrawOptions
& aOptions
,
3643 // Approximating a wide line as a rectangle works only with certain cap styles
3644 // in the general case (butt or square). However, if the line width is
3645 // sufficiently thin, we can either ignore the round cap (or treat it like
3646 // square for zero-length lines) without causing objectionable artifacts.
3647 // Lines may sometimes be used in closed paths that immediately reverse back,
3648 // in which case we need to use mLineJoin instead of mLineCap to determine the
3651 aClosed
? (aStrokeOptions
.mLineJoin
== JoinStyle::ROUND
? CapStyle::ROUND
3653 : aStrokeOptions
.mLineCap
;
3654 if (mWebglValid
&& SupportsPattern(aPattern
) &&
3655 (capStyle
!= CapStyle::ROUND
||
3656 IsThinLine(GetTransform(), aStrokeOptions
)) &&
3657 aStrokeOptions
.mDashPattern
== nullptr && aStrokeOptions
.mLineWidth
> 0) {
3658 // Treat the line as a rectangle whose center-line is the supplied line and
3659 // for which the height is the supplied line width. Generate a matrix that
3660 // maps the X axis to the orientation of the line and the Y axis to the
3661 // normal vector to the line. This only works if the line caps are squared,
3662 // as rounded rectangles are currently not supported for round line caps.
3663 Point start
= aStart
;
3664 Point dirX
= aEnd
- aStart
;
3666 float dirLen
= dirX
.Length();
3667 float scale
= aStrokeOptions
.mLineWidth
;
3668 if (dirLen
== 0.0f
) {
3669 // If the line is zero-length, then only a cap is rendered.
3671 case CapStyle::BUTT
:
3672 // The cap doesn't extend beyond the line so nothing is drawn.
3674 case CapStyle::ROUND
:
3675 case CapStyle::SQUARE
:
3676 // Draw a unit square centered at the single point.
3677 dirX
= Point(scale
, 0.0f
);
3678 dirY
= Point(0.0f
, scale
);
3679 // Offset the start by half a unit.
3680 start
.x
-= 0.5f
* scale
;
3684 // Make the scale map to a single unit length.
3686 dirY
= Point(-dirX
.y
, dirX
.x
) * scale
;
3687 if (capStyle
== CapStyle::SQUARE
) {
3688 // Offset the start by half a unit.
3689 start
-= (dirX
* scale
) * 0.5f
;
3690 // Ensure the extent also accounts for the start and end cap.
3691 dirX
+= dirX
* scale
;
3694 Matrix
lineXform(dirX
.x
, dirX
.y
, dirY
.x
, dirY
.y
, start
.x
- 0.5f
* dirY
.x
,
3695 start
.y
- 0.5f
* dirY
.y
);
3696 AutoRestoreTransform
restore(this);
3697 ConcatTransform(lineXform
);
3698 if (DrawRect(Rect(0, 0, 1, 1), aPattern
, aOptions
, Nothing(), nullptr, true,
3706 void DrawTargetWebgl::StrokeLine(const Point
& aStart
, const Point
& aEnd
,
3707 const Pattern
& aPattern
,
3708 const StrokeOptions
& aStrokeOptions
,
3709 const DrawOptions
& aOptions
) {
3711 MarkSkiaChanged(aOptions
);
3712 mSkia
->StrokeLine(aStart
, aEnd
, aPattern
, aStrokeOptions
, aOptions
);
3713 } else if (!StrokeLineAccel(aStart
, aEnd
, aPattern
, aStrokeOptions
,
3715 // If the stroke options are unsupported, then transform the line to a path
3716 // so it can be cached.
3718 skiaPath
.moveTo(PointToSkPoint(aStart
));
3719 skiaPath
.lineTo(PointToSkPoint(aEnd
));
3720 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
3721 DrawPath(path
, aPattern
, aOptions
, &aStrokeOptions
, true);
3725 void DrawTargetWebgl::Stroke(const Path
* aPath
, const Pattern
& aPattern
,
3726 const StrokeOptions
& aStrokeOptions
,
3727 const DrawOptions
& aOptions
) {
3728 if (!aPath
|| aPath
->GetBackendType() != BackendType::SKIA
) {
3731 const auto& skiaPath
= static_cast<const PathSkia
*>(aPath
)->GetPath();
3733 MarkSkiaChanged(aOptions
);
3734 mSkia
->Stroke(aPath
, aPattern
, aStrokeOptions
, aOptions
);
3738 // Avoid using Skia's isLine here because some paths erroneously include a
3739 // closePath at the end, causing isLine to not detect the line. In that case
3740 // we just draw a line in reverse right over the original line.
3741 int numVerbs
= skiaPath
.countVerbs();
3742 bool allowStrokeAlpha
= false;
3743 if (numVerbs
>= 2 && numVerbs
<= 3) {
3745 skiaPath
.getVerbs(verbs
, numVerbs
);
3746 if (verbs
[0] == SkPath::kMove_Verb
&& verbs
[1] == SkPath::kLine_Verb
&&
3747 (numVerbs
< 3 || verbs
[2] == SkPath::kClose_Verb
)) {
3748 bool closed
= numVerbs
>= 3;
3749 Point start
= SkPointToPoint(skiaPath
.getPoint(0));
3750 Point end
= SkPointToPoint(skiaPath
.getPoint(1));
3751 if (StrokeLineAccel(start
, end
, aPattern
, aStrokeOptions
, aOptions
,
3754 StrokeLineAccel(end
, start
, aPattern
, aStrokeOptions
, aOptions
, true);
3758 // If accelerated line drawing failed, just treat it as a path.
3759 allowStrokeAlpha
= true;
3763 DrawPath(aPath
, aPattern
, aOptions
, &aStrokeOptions
, allowStrokeAlpha
);
3766 bool DrawTargetWebgl::ShouldUseSubpixelAA(ScaledFont
* aFont
,
3767 const DrawOptions
& aOptions
) {
3768 AntialiasMode aaMode
= aFont
->GetDefaultAAMode();
3769 if (aOptions
.mAntialiasMode
!= AntialiasMode::DEFAULT
) {
3770 aaMode
= aOptions
.mAntialiasMode
;
3772 return GetPermitSubpixelAA() &&
3773 (aaMode
== AntialiasMode::DEFAULT
||
3774 aaMode
== AntialiasMode::SUBPIXEL
) &&
3775 aOptions
.mCompositionOp
== CompositionOp::OP_OVER
;
3778 void DrawTargetWebgl::StrokeGlyphs(ScaledFont
* aFont
,
3779 const GlyphBuffer
& aBuffer
,
3780 const Pattern
& aPattern
,
3781 const StrokeOptions
& aStrokeOptions
,
3782 const DrawOptions
& aOptions
) {
3783 if (!aFont
|| !aBuffer
.mNumGlyphs
) {
3787 bool useSubpixelAA
= ShouldUseSubpixelAA(aFont
, aOptions
);
3789 if (mWebglValid
&& SupportsDrawOptions(aOptions
) &&
3790 aPattern
.GetType() == PatternType::COLOR
&& PrepareContext() &&
3791 mSharedContext
->DrawGlyphsAccel(aFont
, aBuffer
, aPattern
, aOptions
,
3792 &aStrokeOptions
, useSubpixelAA
)) {
3796 if (useSubpixelAA
) {
3797 // Subpixel AA does not support layering because the subpixel masks can't
3798 // blend with the over op.
3801 MarkSkiaChanged(aOptions
);
3803 mSkia
->StrokeGlyphs(aFont
, aBuffer
, aPattern
, aStrokeOptions
, aOptions
);
3806 // Depending on whether we enable subpixel position for a given font, Skia may
3807 // round transformed coordinates differently on each axis. By default, text is
3808 // subpixel quantized horizontally and snapped to a whole integer vertical
3809 // baseline. Axis-flip transforms instead snap to horizontal boundaries while
3810 // subpixel quantizing along the vertical. For other types of transforms, Skia
3811 // just applies subpixel quantization to both axes.
3812 // We must duplicate the amount of quantization Skia applies carefully as a
3813 // boundary value such as 0.49 may round to 0.5 with subpixel quantization,
3814 // but if Skia actually snapped it to a whole integer instead, it would round
3815 // down to 0. If a subsequent glyph with offset 0.51 came in, we might
3816 // mistakenly round it down to 0.5, whereas Skia would round it up to 1. Thus
3817 // we would alias 0.49 and 0.51 to the same cache entry, while Skia would
3818 // actually snap the offset to 0 or 1, depending, resulting in mismatched
3820 static inline IntPoint
QuantizeScale(ScaledFont
* aFont
,
3821 const Matrix
& aTransform
) {
3822 if (!aFont
->UseSubpixelPosition()) {
3825 if (aTransform
._12
== 0) {
3826 // Glyphs are rendered subpixel horizontally, so snap vertically.
3829 if (aTransform
._11
== 0) {
3830 // Glyphs are rendered subpixel vertically, so snap horizontally.
3833 // The transform isn't aligned, so don't snap.
3837 // Skia only supports subpixel positioning to the nearest 1/4 fraction. It
3838 // would be wasteful to attempt to cache text runs with positioning that is
3839 // anymore precise than this. To prevent this cache bloat, we quantize the
3840 // transformed glyph positions to the nearest 1/4. The scaling factor for
3841 // the quantization is baked into the transform, so that if subpixel rounding
3842 // is used on a given axis, then the axis will be multiplied by 4 before
3843 // rounding. Since the quantized position is not used for rasterization, the
3844 // transform is safe to modify as such.
3845 static inline IntPoint
QuantizePosition(const Matrix
& aTransform
,
3846 const IntPoint
& aOffset
,
3847 const Point
& aPosition
) {
3848 return RoundedToInt(aTransform
.TransformPoint(aPosition
)) - aOffset
;
3851 // Get a quantized starting offset for the glyph buffer. We want this offset
3852 // to encapsulate the transform and buffer offset while still preserving the
3853 // relative subpixel positions of the glyphs this offset is subtracted from.
3854 static inline IntPoint
QuantizeOffset(const Matrix
& aTransform
,
3855 const IntPoint
& aQuantizeScale
,
3856 const GlyphBuffer
& aBuffer
) {
3858 RoundedToInt(aTransform
.TransformPoint(aBuffer
.mGlyphs
[0].mPosition
));
3859 offset
.x
.value
&= ~(aQuantizeScale
.x
.value
- 1);
3860 offset
.y
.value
&= ~(aQuantizeScale
.y
.value
- 1);
3864 // Hashes a glyph buffer to a single hash value that can be used for quick
3865 // comparisons. Each glyph position is transformed and quantized before
3867 HashNumber
GlyphCacheEntry::HashGlyphs(const GlyphBuffer
& aBuffer
,
3868 const Matrix
& aTransform
,
3869 const IntPoint
& aQuantizeScale
) {
3870 HashNumber hash
= 0;
3871 IntPoint offset
= QuantizeOffset(aTransform
, aQuantizeScale
, aBuffer
);
3872 for (size_t i
= 0; i
< aBuffer
.mNumGlyphs
; i
++) {
3873 const Glyph
& glyph
= aBuffer
.mGlyphs
[i
];
3874 hash
= AddToHash(hash
, glyph
.mIndex
);
3875 IntPoint pos
= QuantizePosition(aTransform
, offset
, glyph
.mPosition
);
3876 hash
= AddToHash(hash
, pos
.x
);
3877 hash
= AddToHash(hash
, pos
.y
);
3882 // Determines if an existing glyph cache entry matches an incoming text run.
3883 inline bool GlyphCacheEntry::MatchesGlyphs(
3884 const GlyphBuffer
& aBuffer
, const DeviceColor
& aColor
,
3885 const Matrix
& aTransform
, const IntPoint
& aQuantizeOffset
,
3886 const IntPoint
& aBoundsOffset
, const IntRect
& aClipRect
, HashNumber aHash
,
3887 const StrokeOptions
* aStrokeOptions
) {
3888 // First check if the hash matches to quickly reject the text run before any
3889 // more expensive checking. If it matches, then check if the color and
3890 // transform are the same.
3891 if (aHash
!= mHash
|| aBuffer
.mNumGlyphs
!= mBuffer
.mNumGlyphs
||
3892 aColor
!= mColor
|| !HasMatchingScale(aTransform
, mTransform
)) {
3895 // Finally check if all glyphs and their quantized positions match.
3896 for (size_t i
= 0; i
< aBuffer
.mNumGlyphs
; i
++) {
3897 const Glyph
& dst
= mBuffer
.mGlyphs
[i
];
3898 const Glyph
& src
= aBuffer
.mGlyphs
[i
];
3899 if (dst
.mIndex
!= src
.mIndex
||
3900 dst
.mPosition
!= Point(QuantizePosition(aTransform
, aQuantizeOffset
,
3905 // Check that stroke options actually match.
3906 if (aStrokeOptions
) {
3907 // If stroking, verify that the entry is also stroked with the same options.
3908 if (!(mStrokeOptions
&& *aStrokeOptions
== *mStrokeOptions
)) {
3911 } else if (mStrokeOptions
) {
3912 // If not stroking, check if the entry is stroked. If so, don't match.
3915 // Verify that the full bounds, once translated and clipped, are equal to the
3917 return (mFullBounds
+ aBoundsOffset
)
3918 .Intersect(aClipRect
)
3919 .IsEqualEdges(GetBounds() + aBoundsOffset
);
3922 GlyphCacheEntry::GlyphCacheEntry(const GlyphBuffer
& aBuffer
,
3923 const DeviceColor
& aColor
,
3924 const Matrix
& aTransform
,
3925 const IntPoint
& aQuantizeScale
,
3926 const IntRect
& aBounds
,
3927 const IntRect
& aFullBounds
, HashNumber aHash
,
3928 StoredStrokeOptions
* aStrokeOptions
)
3929 : CacheEntryImpl
<GlyphCacheEntry
>(aTransform
, aBounds
, aHash
),
3931 mFullBounds(aFullBounds
),
3932 mStrokeOptions(aStrokeOptions
) {
3933 // Store a copy of the glyph buffer with positions already quantized for fast
3934 // comparison later.
3935 Glyph
* glyphs
= new Glyph
[aBuffer
.mNumGlyphs
];
3936 IntPoint offset
= QuantizeOffset(aTransform
, aQuantizeScale
, aBuffer
);
3937 // Make the bounds relative to the offset so we can add a new offset later.
3938 IntPoint
boundsOffset(offset
.x
/ aQuantizeScale
.x
,
3939 offset
.y
/ aQuantizeScale
.y
);
3940 mBounds
-= boundsOffset
;
3941 mFullBounds
-= boundsOffset
;
3942 for (size_t i
= 0; i
< aBuffer
.mNumGlyphs
; i
++) {
3943 Glyph
& dst
= glyphs
[i
];
3944 const Glyph
& src
= aBuffer
.mGlyphs
[i
];
3945 dst
.mIndex
= src
.mIndex
;
3946 dst
.mPosition
= Point(QuantizePosition(aTransform
, offset
, src
.mPosition
));
3948 mBuffer
.mGlyphs
= glyphs
;
3949 mBuffer
.mNumGlyphs
= aBuffer
.mNumGlyphs
;
3952 GlyphCacheEntry::~GlyphCacheEntry() { delete[] mBuffer
.mGlyphs
; }
3954 // Attempt to find a matching entry in the glyph cache. The caller should check
3955 // whether the contained texture handle is valid to determine if it will need to
3956 // render the text run or just reuse the cached texture.
3957 already_AddRefed
<GlyphCacheEntry
> GlyphCache::FindEntry(
3958 const GlyphBuffer
& aBuffer
, const DeviceColor
& aColor
,
3959 const Matrix
& aTransform
, const IntPoint
& aQuantizeScale
,
3960 const IntRect
& aClipRect
, HashNumber aHash
,
3961 const StrokeOptions
* aStrokeOptions
) {
3962 IntPoint offset
= QuantizeOffset(aTransform
, aQuantizeScale
, aBuffer
);
3963 IntPoint
boundsOffset(offset
.x
/ aQuantizeScale
.x
,
3964 offset
.y
/ aQuantizeScale
.y
);
3965 for (const RefPtr
<GlyphCacheEntry
>& entry
: GetChain(aHash
)) {
3966 if (entry
->MatchesGlyphs(aBuffer
, aColor
, aTransform
, offset
, boundsOffset
,
3967 aClipRect
, aHash
, aStrokeOptions
)) {
3968 return do_AddRef(entry
);
3974 // Insert a new entry in the glyph cache.
3975 already_AddRefed
<GlyphCacheEntry
> GlyphCache::InsertEntry(
3976 const GlyphBuffer
& aBuffer
, const DeviceColor
& aColor
,
3977 const Matrix
& aTransform
, const IntPoint
& aQuantizeScale
,
3978 const IntRect
& aBounds
, const IntRect
& aFullBounds
, HashNumber aHash
,
3979 const StrokeOptions
* aStrokeOptions
) {
3980 StoredStrokeOptions
* strokeOptions
= nullptr;
3981 if (aStrokeOptions
) {
3982 strokeOptions
= aStrokeOptions
->Clone();
3983 if (!strokeOptions
) {
3987 RefPtr
<GlyphCacheEntry
> entry
=
3988 new GlyphCacheEntry(aBuffer
, aColor
, aTransform
, aQuantizeScale
, aBounds
,
3989 aFullBounds
, aHash
, strokeOptions
);
3991 return entry
.forget();
3994 GlyphCache::GlyphCache(ScaledFont
* aFont
) : mFont(aFont
) {}
3996 static void ReleaseGlyphCache(void* aPtr
) {
3997 delete static_cast<GlyphCache
*>(aPtr
);
4000 void DrawTargetWebgl::SetPermitSubpixelAA(bool aPermitSubpixelAA
) {
4001 DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA
);
4002 mSkia
->SetPermitSubpixelAA(aPermitSubpixelAA
);
4005 // Check for any color glyphs contained within a rasterized BGRA8 text result.
4006 static bool CheckForColorGlyphs(const RefPtr
<SourceSurface
>& aSurface
) {
4007 if (aSurface
->GetFormat() != SurfaceFormat::B8G8R8A8
) {
4010 RefPtr
<DataSourceSurface
> dataSurf
= aSurface
->GetDataSurface();
4014 DataSourceSurface::ScopedMap
map(dataSurf
, DataSourceSurface::READ
);
4015 if (!map
.IsMapped()) {
4018 IntSize size
= dataSurf
->GetSize();
4019 const uint8_t* data
= map
.GetData();
4020 int32_t stride
= map
.GetStride();
4021 for (int y
= 0; y
< size
.height
; y
++) {
4022 const uint32_t* x
= (const uint32_t*)data
;
4023 const uint32_t* end
= x
+ size
.width
;
4024 for (; x
< end
; x
++) {
4025 // Verify if all components are the same as for premultiplied grayscale.
4026 uint32_t color
= *x
;
4027 uint32_t gray
= color
& 0xFF;
4030 if (color
!= gray
) return true;
4037 // Draws glyphs to the WebGL target by trying to generate a cached texture for
4038 // the text run that can be subsequently reused to quickly render the text run
4039 // without using any software surfaces.
4040 bool DrawTargetWebgl::SharedContext::DrawGlyphsAccel(
4041 ScaledFont
* aFont
, const GlyphBuffer
& aBuffer
, const Pattern
& aPattern
,
4042 const DrawOptions
& aOptions
, const StrokeOptions
* aStrokeOptions
,
4043 bool aUseSubpixelAA
) {
4044 // Whether the font may use bitmaps. If so, we need to render the glyphs with
4045 // color as grayscale bitmaps will use the color while color emoji will not,
4046 // with no easy way to know ahead of time. We currently have to check the
4047 // rasterized result to see if there are any color glyphs. To render subpixel
4048 // masks, we need to know that the rasterized result actually represents a
4049 // subpixel mask rather than try to interpret it as a normal RGBA result such
4050 // as for color emoji.
4051 bool useBitmaps
= !aStrokeOptions
&& aFont
->MayUseBitmaps() &&
4052 aOptions
.mCompositionOp
!= CompositionOp::OP_CLEAR
;
4054 // Look for an existing glyph cache on the font. If not there, create it.
4056 static_cast<GlyphCache
*>(aFont
->GetUserData(&mGlyphCacheKey
));
4058 cache
= new GlyphCache(aFont
);
4059 aFont
->AddUserData(&mGlyphCacheKey
, cache
, ReleaseGlyphCache
);
4060 mGlyphCaches
.insertFront(cache
);
4062 // Hash the incoming text run and looking for a matching entry.
4063 DeviceColor color
= aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
4064 ? DeviceColor(1, 1, 1, 1)
4065 : static_cast<const ColorPattern
&>(aPattern
).mColor
;
4067 // On macOS, depending on whether the text is classified as light-on-dark or
4068 // dark-on-light, we may end up with different amounts of dilation applied, so
4069 // we can't use the same mask in the two circumstances, or the glyphs will be
4070 // dilated incorrectly.
4072 useBitmaps
|| (color
.r
>= 0.33f
&& color
.g
>= 0.33f
&& color
.b
>= 0.33f
&&
4073 color
.r
+ color
.g
+ color
.b
>= 2.0f
);
4075 // On other platforms, we assume no color-dependent dilation.
4076 const bool lightOnDark
= true;
4078 // If the font has bitmaps, use the color directly. Otherwise, the texture
4079 // will hold a grayscale mask, so encode the key's subpixel and light-or-dark
4080 // state in the color.
4081 const Matrix
& currentTransform
= GetTransform();
4082 IntPoint quantizeScale
= QuantizeScale(aFont
, currentTransform
);
4083 Matrix quantizeTransform
= currentTransform
;
4084 quantizeTransform
.PostScale(quantizeScale
.x
, quantizeScale
.y
);
4086 GlyphCacheEntry::HashGlyphs(aBuffer
, quantizeTransform
, quantizeScale
);
4087 DeviceColor colorOrMask
=
4090 : DeviceColor::Mask(aUseSubpixelAA
? 1 : 0, lightOnDark
? 1 : 0);
4091 IntRect
clipRect(IntPoint(), mViewportSize
);
4092 RefPtr
<GlyphCacheEntry
> entry
=
4093 cache
->FindEntry(aBuffer
, colorOrMask
, quantizeTransform
, quantizeScale
,
4094 clipRect
, hash
, aStrokeOptions
);
4096 // For small text runs, bounds computations can be expensive relative to the
4097 // cost of looking up a cache result. Avoid doing local bounds computations
4098 // until actually inserting the entry into the cache.
4099 Maybe
<Rect
> bounds
= mCurrentTarget
->mSkia
->GetGlyphLocalBounds(
4100 aFont
, aBuffer
, aPattern
, aStrokeOptions
, aOptions
);
4104 // Transform the local bounds into device space so that we know how big
4105 // the cached texture will be.
4106 Rect xformBounds
= currentTransform
.TransformBounds(*bounds
);
4107 // Check if the transform flattens out the bounds before rounding.
4108 if (xformBounds
.IsEmpty()) {
4111 IntRect fullBounds
= RoundedOut(currentTransform
.TransformBounds(*bounds
));
4112 IntRect clipBounds
= fullBounds
.Intersect(clipRect
);
4113 // Check if the bounds are completely clipped out.
4114 if (clipBounds
.IsEmpty()) {
4117 entry
= cache
->InsertEntry(aBuffer
, colorOrMask
, quantizeTransform
,
4118 quantizeScale
, clipBounds
, fullBounds
, hash
,
4125 // The bounds of the entry may have a different transform offset from the
4126 // bounds of the currently drawn text run. The entry bounds are relative to
4127 // the entry's quantized offset already, so just move the bounds to the new
4129 IntRect intBounds
= entry
->GetBounds();
4130 IntPoint newOffset
=
4131 QuantizeOffset(quantizeTransform
, quantizeScale
, aBuffer
);
4133 IntPoint(newOffset
.x
/ quantizeScale
.x
, newOffset
.y
/ quantizeScale
.y
);
4134 // Ensure there is a clear border around the text. This must be applied only
4135 // after clipping so that we always have some border texels for filtering.
4136 intBounds
.Inflate(2);
4138 RefPtr
<TextureHandle
> handle
= entry
->GetHandle();
4139 if (handle
&& handle
->IsValid()) {
4140 // If there is an entry with a valid cached texture handle, then try
4141 // to draw with that. If that for some reason failed, then fall back
4142 // to using the Skia target as that means we were preventing from
4143 // drawing to the WebGL context based on something other than the
4145 SurfacePattern
pattern(nullptr, ExtendMode::CLAMP
,
4146 Matrix::Translation(intBounds
.TopLeft()));
4147 if (DrawRectAccel(Rect(intBounds
), pattern
, aOptions
,
4148 useBitmaps
? Nothing() : Some(color
), &handle
, false,
4155 // If we get here, either there wasn't a cached texture handle or it
4156 // wasn't valid. Render the text run into a temporary target.
4157 RefPtr
<DrawTargetSkia
> textDT
= new DrawTargetSkia
;
4158 if (textDT
->Init(intBounds
.Size(),
4159 lightOnDark
&& !useBitmaps
&& !aUseSubpixelAA
4161 : SurfaceFormat::B8G8R8A8
)) {
4163 // If rendering dark-on-light text, we need to clear the background to
4164 // white while using an opaque alpha value to allow this.
4165 textDT
->FillRect(Rect(IntRect(IntPoint(), intBounds
.Size())),
4166 ColorPattern(DeviceColor(1, 1, 1, 1)),
4167 DrawOptions(1.0f
, CompositionOp::OP_OVER
));
4169 textDT
->SetTransform(currentTransform
*
4170 Matrix::Translation(-intBounds
.TopLeft()));
4171 textDT
->SetPermitSubpixelAA(aUseSubpixelAA
);
4172 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
,
4173 aOptions
.mAntialiasMode
);
4174 // If bitmaps might be used, then we have to supply the color, as color
4175 // emoji may ignore it while grayscale bitmaps may use it, with no way to
4176 // know ahead of time. Otherwise, assume the output will be a mask and
4177 // just render it white to determine intensity. Depending on whether the
4178 // text is light or dark, we render white or black text respectively.
4179 ColorPattern
colorPattern(
4180 useBitmaps
? color
: DeviceColor::Mask(lightOnDark
? 1 : 0, 1));
4181 if (aStrokeOptions
) {
4182 textDT
->StrokeGlyphs(aFont
, aBuffer
, colorPattern
, *aStrokeOptions
,
4185 textDT
->FillGlyphs(aFont
, aBuffer
, colorPattern
, drawOptions
);
4188 uint8_t* data
= nullptr;
4191 SurfaceFormat format
= SurfaceFormat::UNKNOWN
;
4192 if (!textDT
->LockBits(&data
, &size
, &stride
, &format
)) {
4195 uint8_t* row
= data
;
4196 for (int y
= 0; y
< size
.height
; ++y
) {
4198 for (int x
= 0; x
< size
.width
; ++x
) {
4199 // If rendering dark-on-light text, we need to invert the final mask
4200 // so that it is in the expected white text on transparent black
4201 // format. The alpha will be initialized to the largest of the
4203 px
[0] = 255 - px
[0];
4204 px
[1] = 255 - px
[1];
4205 px
[2] = 255 - px
[2];
4206 px
[3] = std::max(px
[0], std::max(px
[1], px
[2]));
4211 textDT
->ReleaseBits(data
);
4213 RefPtr
<SourceSurface
> textSurface
= textDT
->Snapshot();
4215 // If we don't expect the text surface to contain color glyphs
4216 // such as from subpixel AA, then do one final check to see if
4217 // any ended up in the result. If not, extract the alpha values
4218 // from the surface so we can render it as a mask.
4219 if (textSurface
->GetFormat() != SurfaceFormat::A8
&&
4220 !CheckForColorGlyphs(textSurface
)) {
4221 textSurface
= ExtractAlpha(textSurface
, !useBitmaps
);
4223 // Failed extracting alpha for the text surface...
4227 // Attempt to upload the rendered text surface into a texture
4228 // handle and draw it.
4229 SurfacePattern
pattern(textSurface
, ExtendMode::CLAMP
,
4230 Matrix::Translation(intBounds
.TopLeft()));
4231 if (DrawRectAccel(Rect(intBounds
), pattern
, aOptions
,
4232 useBitmaps
? Nothing() : Some(color
), &handle
, false,
4235 // If drawing succeeded, then the text surface was uploaded to
4236 // a texture handle. Assign it to the glyph cache entry.
4237 entry
->Link(handle
);
4239 // If drawing failed, remove the entry from the cache.
4249 void DrawTargetWebgl::FillGlyphs(ScaledFont
* aFont
, const GlyphBuffer
& aBuffer
,
4250 const Pattern
& aPattern
,
4251 const DrawOptions
& aOptions
) {
4252 if (!aFont
|| !aBuffer
.mNumGlyphs
) {
4256 bool useSubpixelAA
= ShouldUseSubpixelAA(aFont
, aOptions
);
4258 if (mWebglValid
&& SupportsDrawOptions(aOptions
) &&
4259 aPattern
.GetType() == PatternType::COLOR
&& PrepareContext() &&
4260 mSharedContext
->DrawGlyphsAccel(aFont
, aBuffer
, aPattern
, aOptions
,
4261 nullptr, useSubpixelAA
)) {
4265 // If not able to cache the text run to a texture, then just fall back to
4266 // drawing with the Skia target.
4267 if (useSubpixelAA
) {
4268 // Subpixel AA does not support layering because the subpixel masks can't
4269 // blend with the over op.
4272 MarkSkiaChanged(aOptions
);
4274 mSkia
->FillGlyphs(aFont
, aBuffer
, aPattern
, aOptions
);
4277 void DrawTargetWebgl::SharedContext::WaitForShmem(DrawTargetWebgl
* aTarget
) {
4278 if (mWaitForShmem
) {
4279 // GetError is a sync IPDL call that forces all dispatched commands to be
4280 // flushed. Once it returns, we are certain that any commands processing
4281 // the Shmem have finished.
4282 (void)mWebgl
->GetError();
4283 mWaitForShmem
= false;
4284 // The sync IPDL call can cause expensive round-trips to add up over time,
4285 // so account for that here.
4287 aTarget
->mProfile
.OnReadback();
4292 void DrawTargetWebgl::MarkSkiaChanged(const DrawOptions
& aOptions
) {
4293 if (SupportsLayering(aOptions
)) {
4296 // If the Skia context needs initialization, clear it and enable layering.
4301 mSkiaLayerClear
= mIsClear
;
4302 mSkia
->DetachAllSnapshots();
4303 if (mSkiaLayerClear
) {
4304 // Avoid blending later by making sure the layer background is filled
4305 // with opaque alpha values if necessary.
4306 mSkiaNoClip
->FillRect(Rect(mSkiaNoClip
->GetRect()), GetClearPattern(),
4307 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
4309 mSkiaNoClip
->ClearRect(Rect(mSkiaNoClip
->GetRect()));
4313 // The WebGL context is no longer up-to-date.
4314 mWebglValid
= false;
4317 // For other composition ops, just overwrite the Skia data.
4322 // Attempts to read the contents of the WebGL context into the Skia target.
4323 void DrawTargetWebgl::ReadIntoSkia() {
4328 uint8_t* data
= nullptr;
4331 SurfaceFormat format
;
4333 // If the WebGL target is still clear, then just clear the Skia target.
4334 mSkia
->DetachAllSnapshots();
4335 mSkiaNoClip
->FillRect(Rect(mSkiaNoClip
->GetRect()), GetClearPattern(),
4336 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
4338 // If there's no existing snapshot and we can successfully map the Skia
4339 // target for reading, then try to read into that.
4340 if (!mSnapshot
&& mSkia
->LockBits(&data
, &size
, &stride
, &format
)) {
4341 (void)ReadInto(data
, stride
);
4342 mSkia
->ReleaseBits(data
);
4343 } else if (RefPtr
<SourceSurface
> snapshot
= Snapshot()) {
4344 // Otherwise, fall back to getting a snapshot from WebGL if available
4345 // and then copying that to Skia.
4346 mSkia
->CopySurface(snapshot
, GetRect(), IntPoint(0, 0));
4348 // Signal that we've hit a complete software fallback.
4349 mProfile
.OnFallback();
4353 // The Skia data is flat after reading, so disable any layering.
4357 // Reads data from the WebGL context and blends it with the current Skia layer.
4358 void DrawTargetWebgl::FlattenSkia() {
4359 if (!mSkiaValid
|| !mSkiaLayer
) {
4363 if (mSkiaLayerClear
) {
4364 // If the WebGL target is clear, then there is nothing to blend.
4367 if (RefPtr
<DataSourceSurface
> base
= ReadSnapshot()) {
4368 mSkia
->DetachAllSnapshots();
4369 mSkiaNoClip
->DrawSurface(base
, Rect(GetRect()), Rect(GetRect()),
4370 DrawSurfaceOptions(SamplingFilter::POINT
),
4371 DrawOptions(1.f
, CompositionOp::OP_DEST_OVER
));
4375 // Attempts to draw the contents of the Skia target into the WebGL context.
4376 bool DrawTargetWebgl::FlushFromSkia() {
4377 // If the WebGL context has been lost, then mark it as invalid and fail.
4378 if (mSharedContext
->IsContextLost()) {
4379 mWebglValid
= false;
4382 // The WebGL target is already valid, so there is nothing to do.
4386 // Ensure that DrawRect doesn't recursively call into FlushFromSkia. If
4387 // the Skia target isn't valid, then it doesn't matter what is in the the
4388 // WebGL target either, so only try to blend if there is a valid Skia target.
4391 AutoRestoreContext
restore(this);
4393 // If the Skia target is clear, then there is no need to use a snapshot.
4394 // Directly clear the WebGL target instead.
4396 if (!DrawRect(Rect(GetRect()), GetClearPattern(),
4397 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
), Nothing(),
4398 nullptr, false, false, true)) {
4399 mWebglValid
= false;
4405 RefPtr
<SourceSurface
> skiaSnapshot
= mSkia
->Snapshot();
4406 if (!skiaSnapshot
) {
4407 // There's a valid Skia target to draw to, but for some reason there is
4408 // no available snapshot, so just keep using the Skia target.
4409 mWebglValid
= false;
4413 // If there is no layer, then just upload it directly.
4415 if (PrepareContext(false) && MarkChanged()) {
4416 if (RefPtr
<DataSourceSurface
> data
= skiaSnapshot
->GetDataSurface()) {
4417 mSharedContext
->UploadSurface(data
, mFormat
, GetRect(), IntPoint(),
4418 false, false, mTex
);
4422 // Failed to upload the Skia snapshot.
4423 mWebglValid
= false;
4427 SurfacePattern
pattern(skiaSnapshot
, ExtendMode::CLAMP
);
4428 // If there is a layer, blend the snapshot with the WebGL context.
4429 if (!DrawRect(Rect(GetRect()), pattern
,
4430 DrawOptions(1.0f
, CompositionOp::OP_OVER
), Nothing(),
4431 &mSnapshotTexture
, false, false, true, true)) {
4432 // If accelerated drawing failed for some reason, then leave the Skia
4433 // target unchanged.
4434 mWebglValid
= false;
4441 void DrawTargetWebgl::UsageProfile::BeginFrame() {
4442 // Reset the usage profile counters for the new frame.
4451 void DrawTargetWebgl::UsageProfile::EndFrame() {
4452 bool failed
= false;
4453 // If we hit a complete fallback to software rendering, or if cache misses
4454 // were more than cutoff ratio of all requests, then we consider the frame as
4455 // having failed performance profiling.
4457 StaticPrefs::gfx_canvas_accelerated_profile_cache_miss_ratio();
4458 if (mFallbacks
> 0 ||
4459 float(mCacheMisses
+ mReadbacks
+ mLayers
) >
4460 cacheRatio
* float(mCacheMisses
+ mCacheHits
+ mUncachedDraws
+
4461 mReadbacks
+ mLayers
)) {
4470 bool DrawTargetWebgl::UsageProfile::RequiresRefresh() const {
4471 // If we've rendered at least the required number of frames for a profile and
4472 // more than the cutoff ratio of frames did not meet performance criteria,
4473 // then we should stop using an accelerated canvas.
4474 uint32_t profileFrames
= StaticPrefs::gfx_canvas_accelerated_profile_frames();
4475 if (!profileFrames
|| mFrameCount
< profileFrames
) {
4479 StaticPrefs::gfx_canvas_accelerated_profile_fallback_ratio();
4480 return mFailedFrames
> failRatio
* mFrameCount
;
4483 void DrawTargetWebgl::SharedContext::CachePrefs() {
4484 uint32_t capacity
= StaticPrefs::gfx_canvas_accelerated_gpu_path_size() << 20;
4485 if (capacity
!= mPathVertexCapacity
) {
4486 mPathVertexCapacity
= capacity
;
4488 mPathCache
->ClearVertexRanges();
4490 if (mPathVertexBuffer
) {
4491 ResetPathVertexBuffer();
4495 mPathMaxComplexity
=
4496 StaticPrefs::gfx_canvas_accelerated_gpu_path_complexity();
4498 mPathAAStroke
= StaticPrefs::gfx_canvas_accelerated_aa_stroke_enabled();
4499 mPathWGRStroke
= StaticPrefs::gfx_canvas_accelerated_stroke_to_fill_path();
4502 // For use within CanvasRenderingContext2D, called on BorrowDrawTarget.
4503 void DrawTargetWebgl::BeginFrame(const IntRect
& aPersistedRect
) {
4504 if (mNeedsPresent
) {
4505 mNeedsPresent
= false;
4506 // If still rendering into the Skia target, switch back to the WebGL
4509 if (aPersistedRect
.IsEmpty()) {
4510 // If nothing needs to persist, just mark the WebGL context valid.
4512 // Even if the Skia framebuffer is marked clear, since the WebGL
4513 // context is not valid, its contents may be out-of-date and not
4514 // necessarily clear.
4521 // Check if we need to clear out any cached because of memory pressure.
4522 mSharedContext
->ClearCachesIfNecessary();
4523 // Cache any prefs for the frame.
4524 mSharedContext
->CachePrefs();
4525 mProfile
.BeginFrame();
4528 // For use within CanvasRenderingContext2D, called on ReturnDrawTarget.
4529 void DrawTargetWebgl::EndFrame() {
4530 if (StaticPrefs::gfx_canvas_accelerated_debug()) {
4531 // Draw a green rectangle in the upper right corner to indicate
4533 IntRect corner
= IntRect(mSize
.width
- 16, 0, 16, 16).Intersect(GetRect());
4534 DrawRect(Rect(corner
), ColorPattern(DeviceColor(0.0f
, 1.0f
, 0.0f
, 1.0f
)),
4535 DrawOptions(), Nothing(), nullptr, false, false);
4537 mProfile
.EndFrame();
4538 // Ensure we're not somehow using more than the allowed texture memory.
4539 mSharedContext
->PruneTextureMemory();
4540 // Signal that we're done rendering the frame in case no present occurs.
4541 mSharedContext
->mWebgl
->EndOfFrame();
4542 // Check if we need to clear out any cached because of memory pressure.
4543 mSharedContext
->ClearCachesIfNecessary();
4544 // The framebuffer is dirty, so it needs to be copied to the swapchain.
4545 mNeedsPresent
= true;
4548 Maybe
<layers::SurfaceDescriptor
> DrawTargetWebgl::GetFrontBuffer() {
4549 // Only try to present and retrieve the front buffer if there is a valid
4550 // WebGL framebuffer that can be sent to the compositor. Otherwise, return
4551 // nothing to try to reuse the Skia snapshot.
4552 if (mNeedsPresent
) {
4553 mNeedsPresent
= false;
4554 if (mWebglValid
|| FlushFromSkia()) {
4555 // Copy and swizzle the WebGL framebuffer to the swap chain front buffer.
4556 webgl::SwapChainOptions options
;
4557 options
.bgra
= true;
4558 // Allow async present to be toggled on for accelerated Canvas2D
4559 // independent of WebGL via pref.
4560 options
.forceAsyncPresent
=
4561 StaticPrefs::gfx_canvas_accelerated_async_present();
4562 mSharedContext
->mWebgl
->CopyToSwapChain(mFramebuffer
, options
);
4566 return mSharedContext
->mWebgl
->GetFrontBuffer(mFramebuffer
);
4571 already_AddRefed
<DrawTarget
> DrawTargetWebgl::CreateSimilarDrawTarget(
4572 const IntSize
& aSize
, SurfaceFormat aFormat
) const {
4573 return mSkia
->CreateSimilarDrawTarget(aSize
, aFormat
);
4576 bool DrawTargetWebgl::CanCreateSimilarDrawTarget(const IntSize
& aSize
,
4577 SurfaceFormat aFormat
) const {
4578 return mSkia
->CanCreateSimilarDrawTarget(aSize
, aFormat
);
4581 RefPtr
<DrawTarget
> DrawTargetWebgl::CreateClippedDrawTarget(
4582 const Rect
& aBounds
, SurfaceFormat aFormat
) {
4583 return mSkia
->CreateClippedDrawTarget(aBounds
, aFormat
);
4586 already_AddRefed
<SourceSurface
> DrawTargetWebgl::CreateSourceSurfaceFromData(
4587 unsigned char* aData
, const IntSize
& aSize
, int32_t aStride
,
4588 SurfaceFormat aFormat
) const {
4589 return mSkia
->CreateSourceSurfaceFromData(aData
, aSize
, aStride
, aFormat
);
4592 already_AddRefed
<SourceSurface
>
4593 DrawTargetWebgl::CreateSourceSurfaceFromNativeSurface(
4594 const NativeSurface
& aSurface
) const {
4595 return mSkia
->CreateSourceSurfaceFromNativeSurface(aSurface
);
4598 already_AddRefed
<SourceSurface
> DrawTargetWebgl::OptimizeSourceSurface(
4599 SourceSurface
* aSurface
) const {
4600 if (aSurface
->GetType() == SurfaceType::WEBGL
) {
4601 return do_AddRef(aSurface
);
4603 return mSkia
->OptimizeSourceSurface(aSurface
);
4606 already_AddRefed
<SourceSurface
>
4607 DrawTargetWebgl::OptimizeSourceSurfaceForUnknownAlpha(
4608 SourceSurface
* aSurface
) const {
4609 return mSkia
->OptimizeSourceSurfaceForUnknownAlpha(aSurface
);
4612 already_AddRefed
<GradientStops
> DrawTargetWebgl::CreateGradientStops(
4613 GradientStop
* aStops
, uint32_t aNumStops
, ExtendMode aExtendMode
) const {
4614 return mSkia
->CreateGradientStops(aStops
, aNumStops
, aExtendMode
);
4617 already_AddRefed
<FilterNode
> DrawTargetWebgl::CreateFilter(FilterType aType
) {
4618 return mSkia
->CreateFilter(aType
);
4621 void DrawTargetWebgl::DrawFilter(FilterNode
* aNode
, const Rect
& aSourceRect
,
4622 const Point
& aDestPoint
,
4623 const DrawOptions
& aOptions
) {
4624 MarkSkiaChanged(aOptions
);
4625 mSkia
->DrawFilter(aNode
, aSourceRect
, aDestPoint
, aOptions
);
4628 bool DrawTargetWebgl::Draw3DTransformedSurface(SourceSurface
* aSurface
,
4629 const Matrix4x4
& aMatrix
) {
4631 return mSkia
->Draw3DTransformedSurface(aSurface
, aMatrix
);
4634 void DrawTargetWebgl::PushLayer(bool aOpaque
, Float aOpacity
,
4635 SourceSurface
* aMask
,
4636 const Matrix
& aMaskTransform
,
4637 const IntRect
& aBounds
, bool aCopyBackground
) {
4638 PushLayerWithBlend(aOpaque
, aOpacity
, aMask
, aMaskTransform
, aBounds
,
4639 aCopyBackground
, CompositionOp::OP_OVER
);
4642 void DrawTargetWebgl::PushLayerWithBlend(bool aOpaque
, Float aOpacity
,
4643 SourceSurface
* aMask
,
4644 const Matrix
& aMaskTransform
,
4645 const IntRect
& aBounds
,
4646 bool aCopyBackground
,
4647 CompositionOp aCompositionOp
) {
4648 MarkSkiaChanged(DrawOptions(aOpacity
, aCompositionOp
));
4649 mSkia
->PushLayerWithBlend(aOpaque
, aOpacity
, aMask
, aMaskTransform
, aBounds
,
4650 aCopyBackground
, aCompositionOp
);
4654 void DrawTargetWebgl::PopLayer() {
4655 MOZ_ASSERT(mSkiaValid
);
4656 MOZ_ASSERT(mLayerDepth
> 0);
4661 } // namespace mozilla::gfx