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