1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "DrawTargetWebglInternal.h"
8 #include "SourceSurfaceWebgl.h"
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/StaticPrefs_gfx.h"
12 #include "mozilla/gfx/AAStroke.h"
13 #include "mozilla/gfx/Blur.h"
14 #include "mozilla/gfx/DrawTargetSkia.h"
15 #include "mozilla/gfx/gfxVars.h"
16 #include "mozilla/gfx/Helpers.h"
17 #include "mozilla/gfx/HelpersSkia.h"
18 #include "mozilla/gfx/Logging.h"
19 #include "mozilla/gfx/PathHelpers.h"
20 #include "mozilla/gfx/PathSkia.h"
21 #include "mozilla/gfx/Swizzle.h"
22 #include "mozilla/layers/ImageDataSerializer.h"
23 #include "mozilla/layers/RemoteTextureMap.h"
24 #include "mozilla/widget/ScreenManager.h"
25 #include "skia/include/core/SkPixmap.h"
26 #include "nsContentUtils.h"
28 #include "GLContext.h"
29 #include "WebGLContext.h"
30 #include "WebGLChild.h"
31 #include "WebGLBuffer.h"
32 #include "WebGLFramebuffer.h"
33 #include "WebGLProgram.h"
34 #include "WebGLShader.h"
35 #include "WebGLTexture.h"
36 #include "WebGLVertexArray.h"
38 #include "gfxPlatform.h"
40 namespace mozilla::gfx
{
42 // Inserts (allocates) a rectangle of the requested size into the tree.
43 Maybe
<IntPoint
> TexturePacker::Insert(const IntSize
& aSize
) {
44 // Check if the available space could possibly fit the requested size. If
45 // not, there is no reason to continue searching within this sub-tree.
46 if (mAvailable
< std::min(aSize
.width
, aSize
.height
) ||
47 mBounds
.width
< aSize
.width
|| mBounds
.height
< aSize
.height
) {
51 // If this node has children, then try to insert into each of the children
53 Maybe
<IntPoint
> inserted
= mChildren
[0].Insert(aSize
);
55 inserted
= mChildren
[1].Insert(aSize
);
57 // If the insertion succeeded, adjust the available state to reflect the
58 // remaining space in the children.
60 mAvailable
= std::max(mChildren
[0].mAvailable
, mChildren
[1].mAvailable
);
67 // If we get here, we've encountered a leaf node. First check if its size is
68 // exactly the requested size. If so, mark the node as unavailable and return
70 if (mBounds
.Size() == aSize
) {
72 return Some(mBounds
.TopLeft());
74 // The node is larger than the requested size. Choose the axis which has the
75 // most excess space beyond the requested size and split it so that at least
76 // one of the children matches the requested size for that axis.
77 if (mBounds
.width
- aSize
.width
> mBounds
.height
- aSize
.height
) {
78 mChildren
.reset(new TexturePacker
[2]{
80 IntRect(mBounds
.x
, mBounds
.y
, aSize
.width
, mBounds
.height
)),
81 TexturePacker(IntRect(mBounds
.x
+ aSize
.width
, mBounds
.y
,
82 mBounds
.width
- aSize
.width
, mBounds
.height
))});
84 mChildren
.reset(new TexturePacker
[2]{
86 IntRect(mBounds
.x
, mBounds
.y
, mBounds
.width
, aSize
.height
)),
87 TexturePacker(IntRect(mBounds
.x
, mBounds
.y
+ aSize
.height
,
88 mBounds
.width
, mBounds
.height
- aSize
.height
))});
90 // After splitting, try to insert into the first child, which should usually
91 // be big enough to accomodate the request. Adjust the available state to the
93 Maybe
<IntPoint
> inserted
= mChildren
[0].Insert(aSize
);
94 mAvailable
= std::max(mChildren
[0].mAvailable
, mChildren
[1].mAvailable
);
98 // Removes (frees) a rectangle with the given bounds from the tree.
99 bool TexturePacker::Remove(const IntRect
& aBounds
) {
101 // If there are no children, we encountered a leaf node. Non-zero available
102 // state means that this node was already removed previously. Also, if the
103 // bounds don't contain the request, and assuming the tree was previously
104 // split during insertion, then this node is not the node we're searching
106 if (mAvailable
> 0 || !mBounds
.Contains(aBounds
)) {
109 // The bounds match exactly and it was previously inserted, so in this case
110 // we can just remove it.
111 if (mBounds
== aBounds
) {
112 mAvailable
= std::min(mBounds
.width
, mBounds
.height
);
115 // We need to split this leaf node so that it can exactly match the removed
116 // bounds. We know the leaf node at least contains the removed bounds, but
117 // needs to be subdivided until it has a child node that exactly matches.
118 // Choose the axis to split with the largest amount of excess space. Within
119 // that axis, choose the larger of the space before or after the subrect as
120 // the split point to the new children.
121 if (mBounds
.width
- aBounds
.width
> mBounds
.height
- aBounds
.height
) {
122 int split
= aBounds
.x
- mBounds
.x
> mBounds
.XMost() - aBounds
.XMost()
125 mChildren
.reset(new TexturePacker
[2]{
127 IntRect(mBounds
.x
, mBounds
.y
, split
- mBounds
.x
, mBounds
.height
),
129 TexturePacker(IntRect(split
, mBounds
.y
, mBounds
.XMost() - split
,
133 int split
= aBounds
.y
- mBounds
.y
> mBounds
.YMost() - aBounds
.YMost()
136 mChildren
.reset(new TexturePacker
[2]{
138 IntRect(mBounds
.x
, mBounds
.y
, mBounds
.width
, split
- mBounds
.y
),
141 IntRect(mBounds
.x
, split
, mBounds
.width
, mBounds
.YMost() - split
),
145 // We've encountered a branch node. Determine which of the two child nodes
146 // would possibly contain the removed bounds. We first check which axis the
147 // children were split on and then whether the removed bounds on that axis
148 // are past the start of the second child. Proceed to recurse into that
149 // child node for removal.
150 bool next
= mChildren
[0].mBounds
.x
< mChildren
[1].mBounds
.x
151 ? aBounds
.x
>= mChildren
[1].mBounds
.x
152 : aBounds
.y
>= mChildren
[1].mBounds
.y
;
153 bool removed
= mChildren
[next
? 1 : 0].Remove(aBounds
);
155 if (mChildren
[0].IsFullyAvailable() && mChildren
[1].IsFullyAvailable()) {
157 mAvailable
= std::min(mBounds
.width
, mBounds
.height
);
159 mAvailable
= std::max(mChildren
[0].mAvailable
, mChildren
[1].mAvailable
);
165 BackingTexture::BackingTexture(const IntSize
& aSize
, SurfaceFormat aFormat
,
166 const RefPtr
<WebGLTexture
>& aTexture
)
167 : mSize(aSize
), mFormat(aFormat
), mTexture(aTexture
) {}
169 SharedTexture::SharedTexture(const IntSize
& aSize
, SurfaceFormat aFormat
,
170 const RefPtr
<WebGLTexture
>& aTexture
)
171 : BackingTexture(aSize
, aFormat
, aTexture
),
172 mPacker(IntRect(IntPoint(0, 0), aSize
)) {}
174 SharedTextureHandle::SharedTextureHandle(const IntRect
& aBounds
,
175 SharedTexture
* aTexture
)
176 : mBounds(aBounds
), mTexture(aTexture
) {}
178 already_AddRefed
<SharedTextureHandle
> SharedTexture::Allocate(
179 const IntSize
& aSize
) {
180 RefPtr
<SharedTextureHandle
> handle
;
181 if (Maybe
<IntPoint
> origin
= mPacker
.Insert(aSize
)) {
182 handle
= new SharedTextureHandle(IntRect(*origin
, aSize
), this);
185 return handle
.forget();
188 bool SharedTexture::Free(const SharedTextureHandle
& aHandle
) {
189 if (aHandle
.mTexture
!= this) {
192 if (!mPacker
.Remove(aHandle
.mBounds
)) {
199 StandaloneTexture::StandaloneTexture(const IntSize
& aSize
,
200 SurfaceFormat aFormat
,
201 const RefPtr
<WebGLTexture
>& aTexture
)
202 : BackingTexture(aSize
, aFormat
, aTexture
) {}
204 DrawTargetWebgl::DrawTargetWebgl() = default;
206 inline void SharedContextWebgl::ClearLastTexture(bool aFullClear
) {
207 mLastTexture
= nullptr;
209 mLastClipMask
= nullptr;
213 // Attempts to clear the snapshot state. If the snapshot is only referenced by
214 // this target, then it should simply be destroyed. If it is a WebGL surface in
215 // use by something else, then special cleanup such as reusing the texture or
216 // copy-on-write may be possible.
217 void DrawTargetWebgl::ClearSnapshot(bool aCopyOnWrite
, bool aNeedHandle
) {
221 mSharedContext
->ClearLastTexture();
222 RefPtr
<SourceSurfaceWebgl
> snapshot
= mSnapshot
.forget();
223 if (snapshot
->hasOneRef()) {
227 // WebGL snapshots must be notified that the framebuffer contents will be
228 // changing so that it can copy the data.
229 snapshot
->DrawTargetWillChange(aNeedHandle
);
231 // If not copying, then give the backing texture to the surface for reuse.
232 snapshot
->GiveTexture(
233 mSharedContext
->WrapSnapshot(GetSize(), GetFormat(), mTex
.forget()));
237 DrawTargetWebgl::~DrawTargetWebgl() {
238 ClearSnapshot(false);
239 if (mSharedContext
) {
240 // Force any Skia snapshots to copy the shmem before it deallocs.
242 mSkia
->DetachAllSnapshots();
244 mSharedContext
->ClearLastTexture(true);
246 mFramebuffer
= nullptr;
248 mSharedContext
->mDrawTargetCount
--;
252 SharedContextWebgl::SharedContextWebgl() = default;
254 SharedContextWebgl::~SharedContextWebgl() {
255 // Detect context loss before deletion.
258 mWebgl
->ActiveTexture(0);
260 if (mWGRPathBuilder
) {
261 WGR::wgr_builder_release(mWGRPathBuilder
);
262 mWGRPathBuilder
= nullptr;
265 UnlinkSurfaceTextures();
269 gl::GLContext
* SharedContextWebgl::GetGLContext() {
270 return mWebgl
? mWebgl
->GL() : nullptr;
273 void SharedContextWebgl::EnterTlsScope() {
274 if (mTlsScope
.isSome()) {
277 if (gl::GLContext
* gl
= GetGLContext()) {
278 mTlsScope
= Some(gl
->mUseTLSIsCurrent
);
279 gl::GLContext::InvalidateCurrentContext();
280 gl
->mUseTLSIsCurrent
= true;
284 void SharedContextWebgl::ExitTlsScope() {
285 if (mTlsScope
.isNothing()) {
288 if (gl::GLContext
* gl
= GetGLContext()) {
289 gl
->mUseTLSIsCurrent
= mTlsScope
.value();
291 mTlsScope
= Nothing();
294 // Remove any SourceSurface user data associated with this TextureHandle.
295 inline void SharedContextWebgl::UnlinkSurfaceTexture(
296 const RefPtr
<TextureHandle
>& aHandle
) {
297 if (RefPtr
<SourceSurface
> surface
= aHandle
->GetSurface()) {
298 // Ensure any WebGL snapshot textures get unlinked.
299 if (surface
->GetType() == SurfaceType::WEBGL
) {
300 static_cast<SourceSurfaceWebgl
*>(surface
.get())->OnUnlinkTexture(this);
302 surface
->RemoveUserData(aHandle
->IsShadow() ? &mShadowTextureKey
303 : &mTextureHandleKey
);
307 // Unlinks TextureHandles from any SourceSurface user data.
308 void SharedContextWebgl::UnlinkSurfaceTextures() {
309 for (RefPtr
<TextureHandle
> handle
= mTextureHandles
.getFirst(); handle
;
310 handle
= handle
->getNext()) {
311 UnlinkSurfaceTexture(handle
);
315 // Unlinks GlyphCaches from any ScaledFont user data.
316 void SharedContextWebgl::UnlinkGlyphCaches() {
317 GlyphCache
* cache
= mGlyphCaches
.getFirst();
319 ScaledFont
* font
= cache
->GetFont();
320 // Access the next cache before removing the user data, as it might destroy
322 cache
= cache
->getNext();
323 font
->RemoveUserData(&mGlyphCacheKey
);
327 void SharedContextWebgl::OnMemoryPressure() { mShouldClearCaches
= true; }
329 // Clear out the entire list of texture handles from any source.
330 void SharedContextWebgl::ClearAllTextures() {
331 while (!mTextureHandles
.isEmpty()) {
332 PruneTextureHandle(mTextureHandles
.popLast());
333 --mNumTextureHandles
;
337 // Scan through the shared texture pages looking for any that are empty and
339 void SharedContextWebgl::ClearEmptyTextureMemory() {
340 for (auto pos
= mSharedTextures
.begin(); pos
!= mSharedTextures
.end();) {
341 if (!(*pos
)->HasAllocatedHandles()) {
342 RefPtr
<SharedTexture
> shared
= *pos
;
343 size_t usedBytes
= shared
->UsedBytes();
344 mEmptyTextureMemory
-= usedBytes
;
345 mTotalTextureMemory
-= usedBytes
;
346 pos
= mSharedTextures
.erase(pos
);
353 // If there is a request to clear out the caches because of memory pressure,
354 // then first clear out all the texture handles in the texture cache. If there
355 // are still empty texture pages being kept around, then clear those too.
356 void SharedContextWebgl::ClearCachesIfNecessary() {
357 if (!mShouldClearCaches
.exchange(false)) {
360 mZeroBuffer
= nullptr;
362 if (mEmptyTextureMemory
) {
363 ClearEmptyTextureMemory();
368 // Try to initialize a new WebGL context. Verifies that the requested size does
369 // not exceed the available texture limits and that shader creation succeeded.
370 bool DrawTargetWebgl::Init(const IntSize
& size
, const SurfaceFormat format
,
371 const RefPtr
<SharedContextWebgl
>& aSharedContext
) {
372 MOZ_ASSERT(format
== SurfaceFormat::B8G8R8A8
||
373 format
== SurfaceFormat::B8G8R8X8
);
378 if (!aSharedContext
|| aSharedContext
->IsContextLost() ||
379 aSharedContext
->mDrawTargetCount
>=
380 StaticPrefs::gfx_canvas_accelerated_max_draw_target_count()) {
383 mSharedContext
= aSharedContext
;
384 mSharedContext
->mDrawTargetCount
++;
386 if (size_t(std::max(size
.width
, size
.height
)) >
387 mSharedContext
->mMaxTextureSize
) {
391 if (!CreateFramebuffer()) {
395 size_t byteSize
= layers::ImageDataSerializer::ComputeRGBBufferSize(
396 mSize
, SurfaceFormat::B8G8R8A8
);
401 size_t shmemSize
= mozilla::ipc::SharedMemory::PageAlignedSize(byteSize
);
402 if (NS_WARN_IF(shmemSize
> UINT32_MAX
)) {
403 MOZ_ASSERT_UNREACHABLE("Buffer too big?");
407 auto shmem
= MakeRefPtr
<mozilla::ipc::SharedMemoryBasic
>();
408 if (NS_WARN_IF(!shmem
->Create(shmemSize
)) ||
409 NS_WARN_IF(!shmem
->Map(shmemSize
))) {
413 mShmem
= std::move(shmem
);
414 mShmemSize
= shmemSize
;
416 mSkia
= new DrawTargetSkia
;
417 auto stride
= layers::ImageDataSerializer::ComputeRGBStride(
418 SurfaceFormat::B8G8R8A8
, size
.width
);
419 if (!mSkia
->Init(reinterpret_cast<uint8_t*>(mShmem
->memory()), size
, stride
,
420 SurfaceFormat::B8G8R8A8
, true)) {
424 // Allocate an unclipped copy of the DT pointing to its data.
425 uint8_t* dtData
= nullptr;
427 int32_t dtStride
= 0;
428 SurfaceFormat dtFormat
= SurfaceFormat::UNKNOWN
;
429 if (!mSkia
->LockBits(&dtData
, &dtSize
, &dtStride
, &dtFormat
)) {
432 mSkiaNoClip
= new DrawTargetSkia
;
433 if (!mSkiaNoClip
->Init(dtData
, dtSize
, dtStride
, dtFormat
, true)) {
434 mSkia
->ReleaseBits(dtData
);
437 mSkia
->ReleaseBits(dtData
);
439 SetPermitSubpixelAA(IsOpaque(format
));
443 // If a non-recoverable error occurred that would stop the canvas from initing.
444 static Atomic
<bool> sContextInitError(false);
446 already_AddRefed
<SharedContextWebgl
> SharedContextWebgl::Create() {
447 // If context initialization would fail, don't even try to create a context.
448 if (sContextInitError
) {
451 RefPtr
<SharedContextWebgl
> sharedContext
= new SharedContextWebgl
;
452 if (!sharedContext
->Initialize()) {
455 return sharedContext
.forget();
458 bool SharedContextWebgl::Initialize() {
459 WebGLContextOptions options
= {};
460 options
.alpha
= true;
461 options
.depth
= false;
462 options
.stencil
= false;
463 options
.antialias
= false;
464 options
.preserveDrawingBuffer
= true;
465 options
.failIfMajorPerformanceCaveat
= false;
467 const bool resistFingerprinting
= nsContentUtils::ShouldResistFingerprinting(
468 "Fallback", RFPTarget::WebGLRenderCapability
);
469 const auto initDesc
= webgl::InitContextDesc
{
471 .resistFingerprinting
= resistFingerprinting
,
477 webgl::InitContextResult initResult
;
478 mWebgl
= WebGLContext::Create(nullptr, initDesc
, &initResult
);
480 // There was a non-recoverable error when trying to create a host context.
481 sContextInitError
= true;
485 if (mWebgl
->IsContextLost()) {
490 mMaxTextureSize
= initResult
.limits
.maxTex2dSize
;
493 mRasterizationTruncates
= initResult
.vendor
== gl::GLVendor::ATI
;
498 if (!CreateShaders()) {
499 // There was a non-recoverable error when trying to init shaders.
500 sContextInitError
= true;
505 mWGRPathBuilder
= WGR::wgr_new_builder();
510 inline void SharedContextWebgl::BlendFunc(GLenum aSrcFactor
,
512 mWebgl
->BlendFuncSeparate({}, aSrcFactor
, aDstFactor
, aSrcFactor
, aDstFactor
);
515 void SharedContextWebgl::SetBlendState(CompositionOp aOp
,
516 const Maybe
<DeviceColor
>& aColor
) {
517 if (aOp
== mLastCompositionOp
&& mLastBlendColor
== aColor
) {
520 mLastCompositionOp
= aOp
;
521 mLastBlendColor
= aColor
;
522 // AA is not supported for all composition ops, so switching blend modes may
523 // cause a toggle in AA state. Certain ops such as OP_SOURCE require output
524 // alpha that is blended separately from AA coverage. This would require two
525 // stage blending which can incur a substantial performance penalty, so to
526 // work around this currently we just disable AA for those ops.
528 // Map the composition op to a WebGL blend mode, if possible.
531 case CompositionOp::OP_OVER
:
533 // If a color is supplied, then we blend subpixel text.
534 mWebgl
->BlendColor(aColor
->b
, aColor
->g
, aColor
->r
, 1.0f
);
535 BlendFunc(LOCAL_GL_CONSTANT_COLOR
, LOCAL_GL_ONE_MINUS_SRC_COLOR
);
537 BlendFunc(LOCAL_GL_ONE
, LOCAL_GL_ONE_MINUS_SRC_ALPHA
);
540 case CompositionOp::OP_ADD
:
541 BlendFunc(LOCAL_GL_ONE
, LOCAL_GL_ONE
);
543 case CompositionOp::OP_ATOP
:
544 BlendFunc(LOCAL_GL_DST_ALPHA
, LOCAL_GL_ONE_MINUS_SRC_ALPHA
);
546 case CompositionOp::OP_SOURCE
:
548 // If a color is supplied, then we assume there is clipping or AA. This
549 // requires that we still use an over blend func with the clip/AA alpha,
550 // while filling the interior with the unaltered color. Normally this
551 // would require dual source blending, but we can emulate it with only
553 mWebgl
->BlendColor(aColor
->b
, aColor
->g
, aColor
->r
, aColor
->a
);
554 BlendFunc(LOCAL_GL_CONSTANT_COLOR
, LOCAL_GL_ONE_MINUS_SRC_COLOR
);
559 case CompositionOp::OP_CLEAR
:
560 // Assume the source is an alpha mask for clearing. Be careful to blend in
561 // the correct alpha if the target is opaque.
562 mWebgl
->BlendFuncSeparate(
563 {}, LOCAL_GL_ZERO
, LOCAL_GL_ONE_MINUS_SRC_ALPHA
,
564 IsOpaque(mCurrentTarget
->GetFormat()) ? LOCAL_GL_ONE
: LOCAL_GL_ZERO
,
565 LOCAL_GL_ONE_MINUS_SRC_ALPHA
);
572 mWebgl
->SetEnabled(LOCAL_GL_BLEND
, {}, enabled
);
575 // Ensure the WebGL framebuffer is set to the current target.
576 bool SharedContextWebgl::SetTarget(DrawTargetWebgl
* aDT
) {
577 if (!mWebgl
|| mWebgl
->IsContextLost()) {
580 if (aDT
!= mCurrentTarget
) {
581 mCurrentTarget
= aDT
;
583 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, aDT
->mFramebuffer
);
584 mViewportSize
= aDT
->GetSize();
585 mWebgl
->Viewport(0, 0, mViewportSize
.width
, mViewportSize
.height
);
591 // Replace the current clip rect with a new potentially-AA'd clip rect.
592 void SharedContextWebgl::SetClipRect(const Rect
& aClipRect
) {
593 // Only invalidate the clip rect if it actually changes.
594 if (!mClipAARect
.IsEqualEdges(aClipRect
)) {
595 mClipAARect
= aClipRect
;
596 // Store the integer-aligned bounds.
597 mClipRect
= RoundedOut(aClipRect
);
601 bool SharedContextWebgl::SetClipMask(const RefPtr
<WebGLTexture
>& aTex
) {
602 if (mLastClipMask
!= aTex
) {
606 mWebgl
->ActiveTexture(1);
607 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, aTex
);
608 mWebgl
->ActiveTexture(0);
609 mLastClipMask
= aTex
;
614 bool SharedContextWebgl::SetNoClipMask() {
616 return SetClipMask(mNoClipMask
);
621 mNoClipMask
= mWebgl
->CreateTexture();
625 mWebgl
->ActiveTexture(1);
626 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mNoClipMask
);
627 static const auto solidMask
=
628 std::array
<const uint8_t, 4>{0xFF, 0xFF, 0xFF, 0xFF};
629 mWebgl
->TexImage(0, LOCAL_GL_RGBA8
, {0, 0, 0},
630 {LOCAL_GL_RGBA
, LOCAL_GL_UNSIGNED_BYTE
},
631 {LOCAL_GL_TEXTURE_2D
,
633 gfxAlphaType::NonPremult
,
634 Some(Span
{solidMask
})});
635 InitTexParameters(mNoClipMask
, false);
636 mWebgl
->ActiveTexture(0);
637 mLastClipMask
= mNoClipMask
;
641 inline bool DrawTargetWebgl::ClipStack::operator==(
642 const DrawTargetWebgl::ClipStack
& aOther
) const {
643 // Verify the transform and bounds match.
644 if (!mTransform
.FuzzyEquals(aOther
.mTransform
) ||
645 !mRect
.IsEqualInterior(aOther
.mRect
)) {
648 // Verify the paths match.
650 return !aOther
.mPath
;
653 mPath
->GetBackendType() != aOther
.mPath
->GetBackendType()) {
656 if (mPath
->GetBackendType() != BackendType::SKIA
) {
657 return mPath
== aOther
.mPath
;
659 return static_cast<const PathSkia
*>(mPath
.get())->GetPath() ==
660 static_cast<const PathSkia
*>(aOther
.mPath
.get())->GetPath();
663 // If the clip region can't be approximated by a simple clip rect, then we need
664 // to generate a clip mask that can represent the clip region per-pixel. We
665 // render to the Skia target temporarily, transparent outside the clip region,
666 // opaque inside, and upload this to a texture that can be used by the shaders.
667 bool DrawTargetWebgl::GenerateComplexClipMask() {
668 if (!mClipChanged
|| (mClipMask
&& mCachedClipStack
== mClipStack
)) {
669 mClipChanged
= false;
670 // If the clip mask was already generated, use the cached mask and bounds.
671 mSharedContext
->SetClipMask(mClipMask
);
672 mSharedContext
->SetClipRect(mClipBounds
);
676 // If the Skia target is currently being used, then we can't render the mask
680 RefPtr
<WebGLContext
> webgl
= mSharedContext
->mWebgl
;
686 mClipMask
= webgl
->CreateTexture();
692 // Try to get the bounds of the clip to limit the size of the mask.
693 if (Maybe
<IntRect
> clip
= mSkia
->GetDeviceClipRect(true)) {
696 // If we can't get bounds, then just use the entire viewport.
697 mClipBounds
= GetRect();
699 mClipAARect
= Rect(mClipBounds
);
700 // If initializing the clip mask, then allocate the entire texture to ensure
701 // all pixels get filled with an empty mask regardless. Otherwise, restrict
702 // uploading to only the clip region.
703 RefPtr
<DrawTargetSkia
> dt
= new DrawTargetSkia
;
704 if (!dt
->Init(mClipBounds
.Size(), SurfaceFormat::A8
)) {
707 // Set the clip region and fill the entire inside of it
708 // with opaque white.
709 mCachedClipStack
.clear();
710 for (auto& clipStack
: mClipStack
) {
711 // Record the current state of the clip stack for this mask.
712 mCachedClipStack
.push_back(clipStack
);
714 Matrix(clipStack
.mTransform
).PostTranslate(-mClipBounds
.TopLeft()));
715 if (clipStack
.mPath
) {
716 dt
->PushClip(clipStack
.mPath
);
718 dt
->PushClipRect(clipStack
.mRect
);
721 dt
->SetTransform(Matrix::Translation(-mClipBounds
.TopLeft()));
722 dt
->FillRect(Rect(mClipBounds
), ColorPattern(DeviceColor(1, 1, 1, 1)));
723 // Bind the clip mask for uploading.
724 webgl
->ActiveTexture(1);
725 webgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mClipMask
);
727 mSharedContext
->InitTexParameters(mClipMask
, false);
729 RefPtr
<DataSourceSurface
> data
;
730 if (RefPtr
<SourceSurface
> snapshot
= dt
->Snapshot()) {
731 data
= snapshot
->GetDataSurface();
733 // Finally, upload the texture data and initialize texture storage if
735 if (init
&& mClipBounds
.Size() != mSize
) {
736 mSharedContext
->UploadSurface(nullptr, SurfaceFormat::A8
, GetRect(),
737 IntPoint(), true, true);
740 mSharedContext
->UploadSurface(data
, SurfaceFormat::A8
,
741 IntRect(IntPoint(), mClipBounds
.Size()),
742 mClipBounds
.TopLeft(), init
);
743 webgl
->ActiveTexture(0);
744 // We already bound the texture, so notify the shared context that the clip
745 // mask changed to it.
746 mSharedContext
->mLastClipMask
= mClipMask
;
747 mSharedContext
->SetClipRect(mClipBounds
);
748 // We uploaded a surface, just as if we missed the texture cache, so account
750 mProfile
.OnCacheMiss();
754 bool DrawTargetWebgl::SetSimpleClipRect() {
755 // Determine whether the clipping rectangle is simple enough to accelerate.
756 // Check if there is a device space clip rectangle available from the Skia
758 if (Maybe
<IntRect
> clip
= mSkia
->GetDeviceClipRect(false)) {
759 // If the clip is empty, leave the final integer clip rectangle empty to
760 // trivially discard the draw request.
761 // If the clip rect is larger than the viewport, just set it to the
763 if (!clip
->IsEmpty() && clip
->Contains(GetRect())) {
764 clip
= Some(GetRect());
766 mSharedContext
->SetClipRect(*clip
);
767 mSharedContext
->SetNoClipMask();
771 // There was no pixel-aligned clip rect available, so check the clip stack to
772 // see if there is an AA'd axis-aligned rectangle clip.
773 Rect
rect(GetRect());
774 for (auto& clipStack
: mClipStack
) {
775 // If clip is a path or it has a non-axis-aligned transform, then it is
777 if (clipStack
.mPath
||
778 !clipStack
.mTransform
.PreservesAxisAlignedRectangles()) {
781 // Transform the rect and intersect it with the current clip.
783 clipStack
.mTransform
.TransformBounds(clipStack
.mRect
).Intersect(rect
);
785 mSharedContext
->SetClipRect(rect
);
786 mSharedContext
->SetNoClipMask();
790 // Installs the Skia clip rectangle, if applicable, onto the shared WebGL
791 // context as well as sets the WebGL framebuffer to the current target.
792 bool DrawTargetWebgl::PrepareContext(bool aClipped
) {
794 // If no clipping requested, just set the clip rect to the viewport.
795 mSharedContext
->SetClipRect(GetRect());
796 mSharedContext
->SetNoClipMask();
797 // Ensure the clip gets reset if clipping is later requested for the target.
798 mRefreshClipState
= true;
799 } else if (mRefreshClipState
|| !mSharedContext
->IsCurrentTarget(this)) {
800 // Try to use a simple clip rect if possible. Otherwise, fall back to
801 // generating a clip mask texture that can represent complex clip regions.
802 if (!SetSimpleClipRect() && !GenerateComplexClipMask()) {
805 mClipChanged
= false;
806 mRefreshClipState
= false;
808 return mSharedContext
->SetTarget(this);
811 bool SharedContextWebgl::IsContextLost() const {
812 return !mWebgl
|| mWebgl
->IsContextLost();
815 // Signal to CanvasRenderingContext2D when the WebGL context is lost.
816 bool DrawTargetWebgl::IsValid() const {
817 return mSharedContext
&& !mSharedContext
->IsContextLost();
820 bool DrawTargetWebgl::CanCreate(const IntSize
& aSize
, SurfaceFormat aFormat
) {
821 if (!gfxVars::UseAcceleratedCanvas2D()) {
825 if (!Factory::AllowedSurfaceSize(aSize
)) {
829 // The interpretation of the min-size and max-size follows from the old
830 // SkiaGL prefs. First just ensure that the context is not unreasonably
832 static const int32_t kMinDimension
= 16;
833 if (std::min(aSize
.width
, aSize
.height
) < kMinDimension
) {
837 int32_t minSize
= StaticPrefs::gfx_canvas_accelerated_min_size();
838 if (aSize
.width
* aSize
.height
< minSize
* minSize
) {
842 // Maximum pref allows 3 different options:
843 // 0 means unlimited size,
844 // > 0 means use value as an absolute threshold,
845 // < 0 means use the number of screen pixels as a threshold.
846 int32_t maxSize
= StaticPrefs::gfx_canvas_accelerated_max_size();
848 if (std::max(aSize
.width
, aSize
.height
) > maxSize
) {
851 } else if (maxSize
< 0) {
852 // Default to historical mobile screen size of 980x480, like FishIEtank.
853 // In addition, allow acceleration up to this size even if the screen is
854 // smaller. A lot content expects this size to work well. See Bug 999841
855 static const int32_t kScreenPixels
= 980 * 480;
857 if (RefPtr
<widget::Screen
> screen
=
858 widget::ScreenManager::GetSingleton().GetPrimaryScreen()) {
859 LayoutDeviceIntSize screenSize
= screen
->GetRect().Size();
860 if (aSize
.width
* aSize
.height
>
861 std::max(screenSize
.width
* screenSize
.height
, kScreenPixels
)) {
870 already_AddRefed
<DrawTargetWebgl
> DrawTargetWebgl::Create(
871 const IntSize
& aSize
, SurfaceFormat aFormat
,
872 const RefPtr
<SharedContextWebgl
>& aSharedContext
) {
873 // Validate the size and format.
874 if (!CanCreate(aSize
, aFormat
)) {
878 RefPtr
<DrawTargetWebgl
> dt
= new DrawTargetWebgl
;
879 if (!dt
->Init(aSize
, aFormat
, aSharedContext
) || !dt
->IsValid()) {
886 void* DrawTargetWebgl::GetNativeSurface(NativeSurfaceType aType
) {
888 case NativeSurfaceType::WEBGL_CONTEXT
:
889 // If the context is lost, then don't attempt to access it.
890 if (mSharedContext
->IsContextLost()) {
896 return mSharedContext
->mWebgl
.get();
902 // Wrap a WebGL texture holding a snapshot with a texture handle. Note that
903 // while the texture is still in use as the backing texture of a framebuffer,
904 // it's texture memory is not currently tracked with other texture handles.
905 // Once it is finally orphaned and used as a texture handle, it must be added
906 // to the resource usage totals.
907 already_AddRefed
<TextureHandle
> SharedContextWebgl::WrapSnapshot(
908 const IntSize
& aSize
, SurfaceFormat aFormat
, RefPtr
<WebGLTexture
> aTex
) {
909 // Ensure there is enough space for the texture.
910 size_t usedBytes
= BackingTexture::UsedBytes(aFormat
, aSize
);
911 PruneTextureMemory(usedBytes
, false);
912 // Allocate a handle for the texture
913 RefPtr
<StandaloneTexture
> handle
=
914 new StandaloneTexture(aSize
, aFormat
, aTex
.forget());
915 mStandaloneTextures
.push_back(handle
);
916 mTextureHandles
.insertFront(handle
);
917 mTotalTextureMemory
+= usedBytes
;
918 mUsedTextureMemory
+= usedBytes
;
919 ++mNumTextureHandles
;
920 return handle
.forget();
923 void SharedContextWebgl::SetTexFilter(WebGLTexture
* aTex
, bool aFilter
) {
924 mWebgl
->TexParameter_base(
925 LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_MAG_FILTER
,
926 FloatOrInt(aFilter
? LOCAL_GL_LINEAR
: LOCAL_GL_NEAREST
));
927 mWebgl
->TexParameter_base(
928 LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_MIN_FILTER
,
929 FloatOrInt(aFilter
? LOCAL_GL_LINEAR
: LOCAL_GL_NEAREST
));
932 void SharedContextWebgl::InitTexParameters(WebGLTexture
* aTex
, bool aFilter
) {
933 mWebgl
->TexParameter_base(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_WRAP_S
,
934 FloatOrInt(LOCAL_GL_CLAMP_TO_EDGE
));
935 mWebgl
->TexParameter_base(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_WRAP_T
,
936 FloatOrInt(LOCAL_GL_CLAMP_TO_EDGE
));
937 SetTexFilter(aTex
, aFilter
);
940 // Copy the contents of the WebGL framebuffer into a WebGL texture.
941 already_AddRefed
<TextureHandle
> SharedContextWebgl::CopySnapshot(
942 const IntRect
& aRect
, TextureHandle
* aHandle
) {
943 if (!mWebgl
|| mWebgl
->IsContextLost()) {
947 // If the target is going away, then we can just directly reuse the
948 // framebuffer texture since it will never change.
949 RefPtr
<WebGLTexture
> tex
= mWebgl
->CreateTexture();
954 // If copying from a non-DT source, we have to bind a scratch framebuffer for
957 if (!mScratchFramebuffer
) {
958 mScratchFramebuffer
= mWebgl
->CreateFramebuffer();
960 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mScratchFramebuffer
);
962 webgl::FbAttachInfo attachInfo
;
963 attachInfo
.tex
= aHandle
->GetBackingTexture()->GetWebGLTexture();
964 mWebgl
->FramebufferAttach(LOCAL_GL_FRAMEBUFFER
, LOCAL_GL_COLOR_ATTACHMENT0
,
965 LOCAL_GL_TEXTURE_2D
, attachInfo
);
968 // Create a texture to hold the copy
969 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, tex
);
970 mWebgl
->TexStorage(LOCAL_GL_TEXTURE_2D
, 1, LOCAL_GL_RGBA8
,
971 {uint32_t(aRect
.width
), uint32_t(aRect
.height
), 1});
972 InitTexParameters(tex
);
973 // Copy the framebuffer into the texture
974 mWebgl
->CopyTexImage(LOCAL_GL_TEXTURE_2D
, 0, 0, {0, 0, 0}, {aRect
.x
, aRect
.y
},
975 {uint32_t(aRect
.width
), uint32_t(aRect
.height
)});
978 SurfaceFormat format
=
979 aHandle
? aHandle
->GetFormat() : mCurrentTarget
->GetFormat();
980 already_AddRefed
<TextureHandle
> result
=
981 WrapSnapshot(aRect
.Size(), format
, tex
.forget());
983 // Restore the actual framebuffer after reading is done.
984 if (aHandle
&& mCurrentTarget
) {
985 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mCurrentTarget
->mFramebuffer
);
991 inline DrawTargetWebgl::AutoRestoreContext::AutoRestoreContext(
992 DrawTargetWebgl
* aTarget
)
994 mClipAARect(aTarget
->mSharedContext
->mClipAARect
),
995 mLastClipMask(aTarget
->mSharedContext
->mLastClipMask
) {}
997 inline DrawTargetWebgl::AutoRestoreContext::~AutoRestoreContext() {
998 mTarget
->mSharedContext
->SetClipRect(mClipAARect
);
1000 mTarget
->mSharedContext
->SetClipMask(mLastClipMask
);
1002 mTarget
->mRefreshClipState
= true;
1005 // Utility method to install the target before copying a snapshot.
1006 already_AddRefed
<TextureHandle
> DrawTargetWebgl::CopySnapshot(
1007 const IntRect
& aRect
) {
1008 AutoRestoreContext
restore(this);
1009 if (!PrepareContext(false)) {
1012 return mSharedContext
->CopySnapshot(aRect
);
1015 bool DrawTargetWebgl::HasDataSnapshot() const {
1016 return (mSkiaValid
&& !mSkiaLayer
) || (mSnapshot
&& mSnapshot
->HasReadData());
1019 bool DrawTargetWebgl::PrepareSkia() {
1022 } else if (mSkiaLayer
) {
1028 bool DrawTargetWebgl::EnsureDataSnapshot() {
1029 return HasDataSnapshot() || PrepareSkia();
1032 void DrawTargetWebgl::PrepareShmem() { PrepareSkia(); }
1034 // Borrow a snapshot that may be used by another thread for composition. Only
1035 // Skia snapshots are safe to pass around.
1036 already_AddRefed
<SourceSurface
> DrawTargetWebgl::GetDataSnapshot() {
1038 return mSkia
->Snapshot(mFormat
);
1041 already_AddRefed
<SourceSurface
> DrawTargetWebgl::Snapshot() {
1042 // If already using the Skia fallback, then just snapshot that.
1044 return GetDataSnapshot();
1047 // There's no valid Skia snapshot, so we need to get one from the WebGL
1050 // Create a copy-on-write reference to this target.
1051 mSnapshot
= new SourceSurfaceWebgl(this);
1053 return do_AddRef(mSnapshot
);
1056 // If we need to provide a snapshot for another DrawTargetWebgl that shares the
1057 // same WebGL context, then it is safe to directly return a snapshot. Otherwise,
1058 // we may be exporting to another thread and require a data snapshot.
1059 already_AddRefed
<SourceSurface
> DrawTargetWebgl::GetOptimizedSnapshot(
1060 DrawTarget
* aTarget
) {
1061 if (aTarget
&& aTarget
->GetBackendType() == BackendType::WEBGL
&&
1062 static_cast<DrawTargetWebgl
*>(aTarget
)->mSharedContext
==
1066 return GetDataSnapshot();
1069 // Read from the WebGL context into a buffer. This handles both swizzling BGRA
1070 // to RGBA and flipping the image.
1071 bool SharedContextWebgl::ReadInto(uint8_t* aDstData
, int32_t aDstStride
,
1072 SurfaceFormat aFormat
, const IntRect
& aBounds
,
1073 TextureHandle
* aHandle
) {
1074 MOZ_ASSERT(aFormat
== SurfaceFormat::B8G8R8A8
||
1075 aFormat
== SurfaceFormat::B8G8R8X8
);
1077 // If reading into a new texture, we have to bind it to a scratch framebuffer
1080 if (!mScratchFramebuffer
) {
1081 mScratchFramebuffer
= mWebgl
->CreateFramebuffer();
1083 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mScratchFramebuffer
);
1084 webgl::FbAttachInfo attachInfo
;
1085 attachInfo
.tex
= aHandle
->GetBackingTexture()->GetWebGLTexture();
1086 mWebgl
->FramebufferAttach(LOCAL_GL_FRAMEBUFFER
, LOCAL_GL_COLOR_ATTACHMENT0
,
1087 LOCAL_GL_TEXTURE_2D
, attachInfo
);
1088 } else if (mCurrentTarget
&& mCurrentTarget
->mIsClear
) {
1089 // If reading from a target that is still clear, then avoid the readback by
1090 // just clearing the data.
1091 SkPixmap(MakeSkiaImageInfo(aBounds
.Size(), aFormat
), aDstData
, aDstStride
)
1092 .erase(IsOpaque(aFormat
) ? SK_ColorBLACK
: SK_ColorTRANSPARENT
);
1096 webgl::ReadPixelsDesc desc
;
1097 desc
.srcOffset
= *ivec2::From(aBounds
);
1098 desc
.size
= *uvec2::FromSize(aBounds
);
1099 desc
.packState
.rowLength
= aDstStride
/ 4;
1100 Range
<uint8_t> range
= {aDstData
, size_t(aDstStride
) * aBounds
.height
};
1101 mWebgl
->ReadPixelsInto(desc
, range
);
1103 // Restore the actual framebuffer after reading is done.
1104 if (aHandle
&& mCurrentTarget
) {
1105 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mCurrentTarget
->mFramebuffer
);
1111 already_AddRefed
<DataSourceSurface
> SharedContextWebgl::ReadSnapshot(
1112 TextureHandle
* aHandle
) {
1113 // Allocate a data surface, map it, and read from the WebGL context into the
1115 SurfaceFormat format
= SurfaceFormat::UNKNOWN
;
1118 format
= aHandle
->GetFormat();
1119 bounds
= aHandle
->GetBounds();
1121 format
= mCurrentTarget
->GetFormat();
1122 bounds
= mCurrentTarget
->GetRect();
1124 RefPtr
<DataSourceSurface
> surface
=
1125 Factory::CreateDataSourceSurface(bounds
.Size(), format
);
1129 DataSourceSurface::ScopedMap
dstMap(surface
, DataSourceSurface::WRITE
);
1130 if (!dstMap
.IsMapped() || !ReadInto(dstMap
.GetData(), dstMap
.GetStride(),
1131 format
, bounds
, aHandle
)) {
1134 return surface
.forget();
1137 // Utility method to install the target before reading a snapshot.
1138 bool DrawTargetWebgl::ReadInto(uint8_t* aDstData
, int32_t aDstStride
) {
1139 if (!PrepareContext(false)) {
1143 return mSharedContext
->ReadInto(aDstData
, aDstStride
, GetFormat(), GetRect());
1146 // Utility method to install the target before reading a snapshot.
1147 already_AddRefed
<DataSourceSurface
> DrawTargetWebgl::ReadSnapshot() {
1148 AutoRestoreContext
restore(this);
1149 if (!PrepareContext(false)) {
1152 mProfile
.OnReadback();
1153 return mSharedContext
->ReadSnapshot();
1156 already_AddRefed
<SourceSurface
> DrawTargetWebgl::GetBackingSurface() {
1160 void DrawTargetWebgl::DetachAllSnapshots() {
1161 mSkia
->DetachAllSnapshots();
1165 // Prepare the framebuffer for accelerated drawing. Any cached snapshots will
1166 // be invalidated if not detached and copied here. Ensure the WebGL
1167 // framebuffer's contents are updated if still somehow stored in the Skia
1169 bool DrawTargetWebgl::MarkChanged() {
1171 // Try to copy the target into a new texture if possible.
1172 ClearSnapshot(true, true);
1174 if (!mWebglValid
&& !FlushFromSkia()) {
1182 void DrawTargetWebgl::MarkSkiaChanged(bool aOverwrite
) {
1186 } else if (!mSkiaValid
) {
1187 if (ReadIntoSkia()) {
1188 // Signal that we've hit a complete software fallback.
1189 mProfile
.OnFallback();
1191 } else if (mSkiaLayer
) {
1194 mWebglValid
= false;
1198 // Whether a given composition operator is associative and thus allows drawing
1199 // into a separate layer that can be later composited back into the WebGL
1201 static inline bool SupportsLayering(const DrawOptions
& aOptions
) {
1202 switch (aOptions
.mCompositionOp
) {
1203 case CompositionOp::OP_OVER
:
1204 // Layering is only supported for the default source-over composition op.
1211 void DrawTargetWebgl::MarkSkiaChanged(const DrawOptions
& aOptions
) {
1212 if (SupportsLayering(aOptions
)) {
1214 // If the Skia context needs initialization, clear it and enable layering.
1219 mSkiaLayerClear
= mIsClear
;
1220 mSkia
->DetachAllSnapshots();
1221 if (mSkiaLayerClear
) {
1222 // Avoid blending later by making sure the layer background is filled
1223 // with opaque alpha values if necessary.
1224 mSkiaNoClip
->FillRect(Rect(mSkiaNoClip
->GetRect()), GetClearPattern(),
1225 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
1227 mSkiaNoClip
->ClearRect(Rect(mSkiaNoClip
->GetRect()));
1231 // The WebGL context is no longer up-to-date.
1232 mWebglValid
= false;
1235 // For other composition ops, just overwrite the Skia data.
1240 bool DrawTargetWebgl::LockBits(uint8_t** aData
, IntSize
* aSize
,
1241 int32_t* aStride
, SurfaceFormat
* aFormat
,
1242 IntPoint
* aOrigin
) {
1243 // Can only access pixels if there is valid, flattened Skia data.
1244 if (mSkiaValid
&& !mSkiaLayer
) {
1246 return mSkia
->LockBits(aData
, aSize
, aStride
, aFormat
, aOrigin
);
1251 void DrawTargetWebgl::ReleaseBits(uint8_t* aData
) {
1252 // Can only access pixels if there is valid, flattened Skia data.
1253 if (mSkiaValid
&& !mSkiaLayer
) {
1254 mSkia
->ReleaseBits(aData
);
1258 // Format is x, y, alpha
1259 static const float kRectVertexData
[12] = {0.0f
, 0.0f
, 1.0f
, 1.0f
, 0.0f
, 1.0f
,
1260 1.0f
, 1.0f
, 1.0f
, 0.0f
, 1.0f
, 1.0f
};
1262 // Orphans the contents of the path vertex buffer. The beginning of the buffer
1263 // always contains data for a simple rectangle draw to avoid needing to switch
1265 void SharedContextWebgl::ResetPathVertexBuffer(bool aChanged
) {
1266 mWebgl
->BindBuffer(LOCAL_GL_ARRAY_BUFFER
, mPathVertexBuffer
.get());
1267 mWebgl
->UninitializedBufferData_SizeOnly(
1268 LOCAL_GL_ARRAY_BUFFER
,
1269 std::max(size_t(mPathVertexCapacity
), sizeof(kRectVertexData
)),
1270 LOCAL_GL_DYNAMIC_DRAW
);
1271 mWebgl
->BufferSubData(LOCAL_GL_ARRAY_BUFFER
, 0, sizeof(kRectVertexData
),
1272 (const uint8_t*)kRectVertexData
);
1273 mPathVertexOffset
= sizeof(kRectVertexData
);
1275 mWGROutputBuffer
.reset(
1276 mPathVertexCapacity
> 0
1277 ? new (fallible
) WGR::OutputVertex
[mPathVertexCapacity
/
1278 sizeof(WGR::OutputVertex
)]
1283 // Attempts to create all shaders and resources to be used for drawing commands.
1284 // Returns whether or not this succeeded.
1285 bool SharedContextWebgl::CreateShaders() {
1286 if (!mPathVertexArray
) {
1287 mPathVertexArray
= mWebgl
->CreateVertexArray();
1289 if (!mPathVertexBuffer
) {
1290 mPathVertexBuffer
= mWebgl
->CreateBuffer();
1291 mWebgl
->BindVertexArray(mPathVertexArray
.get());
1292 ResetPathVertexBuffer();
1293 mWebgl
->EnableVertexAttribArray(0);
1295 webgl::VertAttribPointerDesc attribDesc
;
1296 attribDesc
.channels
= 3;
1297 attribDesc
.type
= LOCAL_GL_FLOAT
;
1298 attribDesc
.normalized
= false;
1299 mWebgl
->VertexAttribPointer(0, attribDesc
);
1301 if (!mSolidProgram
) {
1302 // AA is computed by using the basis vectors of the transform to determine
1303 // both the scale and orientation. The scale is then used to extrude the
1304 // rectangle outward by 1 screen-space pixel to account for the AA region.
1305 // The distance to the rectangle edges is passed to the fragment shader in
1306 // an interpolant, biased by 0.5 so it represents the desired coverage. The
1307 // minimum coverage is then chosen by the fragment shader to use as an AA
1308 // coverage value to modulate the color.
1310 "attribute vec3 a_vertex;\n"
1311 "uniform vec2 u_transform[3];\n"
1312 "uniform vec2 u_viewport;\n"
1313 "uniform vec4 u_clipbounds;\n"
1314 "uniform float u_aa;\n"
1315 "varying vec2 v_cliptc;\n"
1316 "varying vec4 v_clipdist;\n"
1317 "varying vec4 v_dist;\n"
1318 "varying float v_alpha;\n"
1320 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1321 " dot(u_transform[1], u_transform[1]));\n"
1322 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1323 " scale *= invScale;\n"
1324 " vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
1326 " vec2 vertex = u_transform[0] * extrude.x +\n"
1327 " u_transform[1] * extrude.y +\n"
1328 " u_transform[2];\n"
1329 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1330 " v_cliptc = vertex / u_viewport;\n"
1331 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
1332 " u_clipbounds.zw - vertex);\n"
1333 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
1334 " v_alpha = a_vertex.z;\n"
1337 "precision mediump float;\n"
1338 "uniform vec4 u_color;\n"
1339 "uniform sampler2D u_clipmask;\n"
1340 "varying highp vec2 v_cliptc;\n"
1341 "varying vec4 v_clipdist;\n"
1342 "varying vec4 v_dist;\n"
1343 "varying float v_alpha;\n"
1345 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1346 " vec4 dist = min(v_dist, v_clipdist);\n"
1347 " dist.xy = min(dist.xy, dist.zw);\n"
1348 " float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
1349 " gl_FragColor = clip * aa * u_color;\n"
1351 RefPtr
<WebGLShader
> vsId
= mWebgl
->CreateShader(LOCAL_GL_VERTEX_SHADER
);
1352 mWebgl
->ShaderSource(*vsId
, vsSource
);
1353 mWebgl
->CompileShader(*vsId
);
1354 if (!mWebgl
->GetCompileResult(*vsId
).success
) {
1357 RefPtr
<WebGLShader
> fsId
= mWebgl
->CreateShader(LOCAL_GL_FRAGMENT_SHADER
);
1358 mWebgl
->ShaderSource(*fsId
, fsSource
);
1359 mWebgl
->CompileShader(*fsId
);
1360 if (!mWebgl
->GetCompileResult(*fsId
).success
) {
1363 mSolidProgram
= mWebgl
->CreateProgram();
1364 mWebgl
->AttachShader(*mSolidProgram
, *vsId
);
1365 mWebgl
->AttachShader(*mSolidProgram
, *fsId
);
1366 mWebgl
->BindAttribLocation(*mSolidProgram
, 0, "a_vertex");
1367 mWebgl
->LinkProgram(*mSolidProgram
);
1368 if (!mWebgl
->GetLinkResult(*mSolidProgram
).success
) {
1371 mSolidProgramViewport
= GetUniformLocation(mSolidProgram
, "u_viewport");
1372 mSolidProgramAA
= GetUniformLocation(mSolidProgram
, "u_aa");
1373 mSolidProgramTransform
= GetUniformLocation(mSolidProgram
, "u_transform");
1374 mSolidProgramColor
= GetUniformLocation(mSolidProgram
, "u_color");
1375 mSolidProgramClipMask
= GetUniformLocation(mSolidProgram
, "u_clipmask");
1376 mSolidProgramClipBounds
= GetUniformLocation(mSolidProgram
, "u_clipbounds");
1377 if (!mSolidProgramViewport
|| !mSolidProgramAA
|| !mSolidProgramTransform
||
1378 !mSolidProgramColor
|| !mSolidProgramClipMask
||
1379 !mSolidProgramClipBounds
) {
1382 mWebgl
->UseProgram(mSolidProgram
);
1383 UniformData(LOCAL_GL_INT
, mSolidProgramClipMask
, Array
<int32_t, 1>{1});
1386 if (!mImageProgram
) {
1388 "attribute vec3 a_vertex;\n"
1389 "uniform vec2 u_viewport;\n"
1390 "uniform vec4 u_clipbounds;\n"
1391 "uniform float u_aa;\n"
1392 "uniform vec2 u_transform[3];\n"
1393 "uniform vec2 u_texmatrix[3];\n"
1394 "varying vec2 v_cliptc;\n"
1395 "varying vec2 v_texcoord;\n"
1396 "varying vec4 v_clipdist;\n"
1397 "varying vec4 v_dist;\n"
1398 "varying float v_alpha;\n"
1400 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1401 " dot(u_transform[1], u_transform[1]));\n"
1402 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1403 " scale *= invScale;\n"
1404 " vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
1406 " vec2 vertex = u_transform[0] * extrude.x +\n"
1407 " u_transform[1] * extrude.y +\n"
1408 " u_transform[2];\n"
1409 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1410 " v_cliptc = vertex / u_viewport;\n"
1411 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
1412 " u_clipbounds.zw - vertex);\n"
1413 " v_texcoord = u_texmatrix[0] * extrude.x +\n"
1414 " u_texmatrix[1] * extrude.y +\n"
1415 " u_texmatrix[2];\n"
1416 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
1417 " v_alpha = a_vertex.z;\n"
1420 "precision mediump float;\n"
1421 "uniform vec4 u_texbounds;\n"
1422 "uniform vec4 u_color;\n"
1423 "uniform float u_swizzle;\n"
1424 "uniform sampler2D u_sampler;\n"
1425 "uniform sampler2D u_clipmask;\n"
1426 "varying highp vec2 v_cliptc;\n"
1427 "varying highp vec2 v_texcoord;\n"
1428 "varying vec4 v_clipdist;\n"
1429 "varying vec4 v_dist;\n"
1430 "varying float v_alpha;\n"
1432 " highp vec2 tc = clamp(v_texcoord, u_texbounds.xy,\n"
1433 " u_texbounds.zw);\n"
1434 " vec4 image = texture2D(u_sampler, tc);\n"
1435 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1436 " vec4 dist = min(v_dist, v_clipdist);\n"
1437 " dist.xy = min(dist.xy, dist.zw);\n"
1438 " float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
1439 " gl_FragColor = clip * aa * u_color *\n"
1440 " mix(image, image.rrrr, u_swizzle);\n"
1442 RefPtr
<WebGLShader
> vsId
= mWebgl
->CreateShader(LOCAL_GL_VERTEX_SHADER
);
1443 mWebgl
->ShaderSource(*vsId
, vsSource
);
1444 mWebgl
->CompileShader(*vsId
);
1445 if (!mWebgl
->GetCompileResult(*vsId
).success
) {
1448 RefPtr
<WebGLShader
> fsId
= mWebgl
->CreateShader(LOCAL_GL_FRAGMENT_SHADER
);
1449 mWebgl
->ShaderSource(*fsId
, fsSource
);
1450 mWebgl
->CompileShader(*fsId
);
1451 if (!mWebgl
->GetCompileResult(*fsId
).success
) {
1454 mImageProgram
= mWebgl
->CreateProgram();
1455 mWebgl
->AttachShader(*mImageProgram
, *vsId
);
1456 mWebgl
->AttachShader(*mImageProgram
, *fsId
);
1457 mWebgl
->BindAttribLocation(*mImageProgram
, 0, "a_vertex");
1458 mWebgl
->LinkProgram(*mImageProgram
);
1459 if (!mWebgl
->GetLinkResult(*mImageProgram
).success
) {
1462 mImageProgramViewport
= GetUniformLocation(mImageProgram
, "u_viewport");
1463 mImageProgramAA
= GetUniformLocation(mImageProgram
, "u_aa");
1464 mImageProgramTransform
= GetUniformLocation(mImageProgram
, "u_transform");
1465 mImageProgramTexMatrix
= GetUniformLocation(mImageProgram
, "u_texmatrix");
1466 mImageProgramTexBounds
= GetUniformLocation(mImageProgram
, "u_texbounds");
1467 mImageProgramSwizzle
= GetUniformLocation(mImageProgram
, "u_swizzle");
1468 mImageProgramColor
= GetUniformLocation(mImageProgram
, "u_color");
1469 mImageProgramSampler
= GetUniformLocation(mImageProgram
, "u_sampler");
1470 mImageProgramClipMask
= GetUniformLocation(mImageProgram
, "u_clipmask");
1471 mImageProgramClipBounds
= GetUniformLocation(mImageProgram
, "u_clipbounds");
1472 if (!mImageProgramViewport
|| !mImageProgramAA
|| !mImageProgramTransform
||
1473 !mImageProgramTexMatrix
|| !mImageProgramTexBounds
||
1474 !mImageProgramSwizzle
|| !mImageProgramColor
|| !mImageProgramSampler
||
1475 !mImageProgramClipMask
|| !mImageProgramClipBounds
) {
1478 mWebgl
->UseProgram(mImageProgram
);
1479 UniformData(LOCAL_GL_INT
, mImageProgramSampler
, Array
<int32_t, 1>{0});
1480 UniformData(LOCAL_GL_INT
, mImageProgramClipMask
, Array
<int32_t, 1>{1});
1485 void SharedContextWebgl::EnableScissor(const IntRect
& aRect
) {
1486 // Only update scissor state if it actually changes.
1487 if (!mLastScissor
.IsEqualEdges(aRect
)) {
1488 mLastScissor
= aRect
;
1489 mWebgl
->Scissor(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
);
1491 if (!mScissorEnabled
) {
1492 mScissorEnabled
= true;
1493 mWebgl
->SetEnabled(LOCAL_GL_SCISSOR_TEST
, {}, true);
1497 void SharedContextWebgl::DisableScissor() {
1498 if (mScissorEnabled
) {
1499 mScissorEnabled
= false;
1500 mWebgl
->SetEnabled(LOCAL_GL_SCISSOR_TEST
, {}, false);
1504 inline ColorPattern
DrawTargetWebgl::GetClearPattern() const {
1505 return ColorPattern(
1506 DeviceColor(0.0f
, 0.0f
, 0.0f
, IsOpaque(mFormat
) ? 1.0f
: 0.0f
));
1509 // Check if the transformed rect would contain the entire viewport.
1510 inline bool DrawTargetWebgl::RectContainsViewport(const Rect
& aRect
) const {
1511 return mTransform
.PreservesAxisAlignedRectangles() &&
1512 MatrixDouble(mTransform
)
1514 RectDouble(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
))
1515 .Contains(RectDouble(GetRect()));
1518 // Ensure that the rect, after transform, is within reasonable precision limits
1519 // such that when transformed and clipped in the shader it will not round bits
1520 // from the mantissa in a way that will diverge in a noticeable way from path
1521 // geometry calculated by the path fallback.
1522 static inline bool RectInsidePrecisionLimits(const Rect
& aRect
,
1523 const Matrix
& aTransform
) {
1524 return Rect(-(1 << 20), -(1 << 20), 2 << 20, 2 << 20)
1525 .Contains(aTransform
.TransformBounds(aRect
));
1528 void DrawTargetWebgl::ClearRect(const Rect
& aRect
) {
1530 // No need to clear anything if the entire framebuffer is already clear.
1534 bool containsViewport
= RectContainsViewport(aRect
);
1535 if (containsViewport
) {
1536 // If the rect encompasses the entire viewport, just clear the viewport
1537 // instead to avoid transform issues.
1538 DrawRect(Rect(GetRect()), GetClearPattern(),
1539 DrawOptions(1.0f
, CompositionOp::OP_CLEAR
), Nothing(), nullptr,
1541 } else if (RectInsidePrecisionLimits(aRect
, mTransform
)) {
1542 // If the rect transform won't stress precision, then just use it.
1543 DrawRect(aRect
, GetClearPattern(),
1544 DrawOptions(1.0f
, CompositionOp::OP_CLEAR
));
1546 // Otherwise, using the transform in the shader may lead to inaccuracies, so
1549 mSkia
->ClearRect(aRect
);
1552 // If the clear rectangle encompasses the entire viewport and is not clipped,
1553 // then mark the target as entirely clear.
1554 if (containsViewport
&& mSharedContext
->IsCurrentTarget(this) &&
1555 !mSharedContext
->HasClipMask() &&
1556 mSharedContext
->mClipAARect
.Contains(Rect(GetRect()))) {
1561 static inline DeviceColor
PremultiplyColor(const DeviceColor
& aColor
,
1562 float aAlpha
= 1.0f
) {
1563 float a
= aColor
.a
* aAlpha
;
1564 return DeviceColor(aColor
.r
* a
, aColor
.g
* a
, aColor
.b
* a
, a
);
1567 // Attempts to create the framebuffer used for drawing and also any relevant
1568 // non-shared resources. Returns whether or not this succeeded.
1569 bool DrawTargetWebgl::CreateFramebuffer() {
1570 RefPtr
<WebGLContext
> webgl
= mSharedContext
->mWebgl
;
1571 if (!mFramebuffer
) {
1572 mFramebuffer
= webgl
->CreateFramebuffer();
1575 mTex
= webgl
->CreateTexture();
1576 webgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mTex
);
1577 webgl
->TexStorage(LOCAL_GL_TEXTURE_2D
, 1, LOCAL_GL_RGBA8
,
1578 {uint32_t(mSize
.width
), uint32_t(mSize
.height
), 1});
1579 mSharedContext
->InitTexParameters(mTex
);
1580 webgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mFramebuffer
);
1581 webgl::FbAttachInfo attachInfo
;
1582 attachInfo
.tex
= mTex
;
1583 webgl
->FramebufferAttach(LOCAL_GL_FRAMEBUFFER
, LOCAL_GL_COLOR_ATTACHMENT0
,
1584 LOCAL_GL_TEXTURE_2D
, attachInfo
);
1585 webgl
->Viewport(0, 0, mSize
.width
, mSize
.height
);
1586 mSharedContext
->DisableScissor();
1587 DeviceColor color
= PremultiplyColor(GetClearPattern().mColor
);
1588 webgl
->ClearColor(color
.b
, color
.g
, color
.r
, color
.a
);
1589 webgl
->Clear(LOCAL_GL_COLOR_BUFFER_BIT
);
1590 mSharedContext
->ClearTarget();
1591 mSharedContext
->ClearLastTexture();
1596 void DrawTargetWebgl::CopySurface(SourceSurface
* aSurface
,
1597 const IntRect
& aSourceRect
,
1598 const IntPoint
& aDestination
) {
1599 // Intersect the source and destination rectangles with the viewport bounds.
1601 IntRect(aDestination
, aSourceRect
.Size()).SafeIntersect(GetRect());
1602 IntRect srcRect
= destRect
- aDestination
+ aSourceRect
.TopLeft();
1603 if (srcRect
.IsEmpty()) {
1609 if (destRect
.Contains(GetRect())) {
1610 // If the the destination would override the entire layer, discard the
1613 } else if (!IsOpaque(aSurface
->GetFormat())) {
1614 // If the surface is not opaque, copying it into the layer results in
1615 // unintended blending rather than a copy to the destination.
1619 // If there is no layer, copying is safe.
1622 mSkia
->CopySurface(aSurface
, srcRect
, destRect
.TopLeft());
1626 IntRect samplingRect
;
1627 if (!mSharedContext
->IsCompatibleSurface(aSurface
)) {
1628 // If this data surface completely overwrites the framebuffer, then just
1629 // copy it to the Skia target.
1630 if (destRect
.Contains(GetRect())) {
1631 MarkSkiaChanged(true);
1632 mSkia
->DetachAllSnapshots();
1633 mSkiaNoClip
->CopySurface(aSurface
, srcRect
, destRect
.TopLeft());
1637 // CopySurface usually only samples a surface once, so don't cache the
1638 // entire surface as it is unlikely to be reused. Limit it to the used
1639 // source rectangle instead.
1640 IntRect surfaceRect
= aSurface
->GetRect();
1641 if (!srcRect
.IsEqualEdges(surfaceRect
)) {
1642 samplingRect
= srcRect
.SafeIntersect(surfaceRect
);
1646 Matrix matrix
= Matrix::Translation(destRect
.TopLeft() - srcRect
.TopLeft());
1647 SurfacePattern
pattern(aSurface
, ExtendMode::CLAMP
, matrix
,
1648 SamplingFilter::POINT
, samplingRect
);
1649 DrawRect(Rect(destRect
), pattern
, DrawOptions(1.0f
, CompositionOp::OP_SOURCE
),
1650 Nothing(), nullptr, false, false);
1653 void DrawTargetWebgl::PushClip(const Path
* aPath
) {
1654 if (aPath
&& aPath
->GetBackendType() == BackendType::SKIA
) {
1655 // Detect if the path is really just a rect to simplify caching.
1656 const PathSkia
* pathSkia
= static_cast<const PathSkia
*>(aPath
);
1657 const SkPath
& skPath
= pathSkia
->GetPath();
1658 SkRect rect
= SkRect::MakeEmpty();
1659 if (skPath
.isRect(&rect
)) {
1660 PushClipRect(SkRectToRect(rect
));
1665 mClipChanged
= true;
1666 mRefreshClipState
= true;
1667 mSkia
->PushClip(aPath
);
1669 mClipStack
.push_back({GetTransform(), Rect(), aPath
});
1672 void DrawTargetWebgl::PushClipRect(const Rect
& aRect
) {
1673 mClipChanged
= true;
1674 mRefreshClipState
= true;
1675 mSkia
->PushClipRect(aRect
);
1677 mClipStack
.push_back({GetTransform(), aRect
, nullptr});
1680 void DrawTargetWebgl::PushDeviceSpaceClipRects(const IntRect
* aRects
,
1682 mClipChanged
= true;
1683 mRefreshClipState
= true;
1684 mSkia
->PushDeviceSpaceClipRects(aRects
, aCount
);
1686 for (uint32_t i
= 0; i
< aCount
; i
++) {
1687 mClipStack
.push_back({Matrix(), Rect(aRects
[i
]), nullptr});
1691 void DrawTargetWebgl::PopClip() {
1692 mClipChanged
= true;
1693 mRefreshClipState
= true;
1696 mClipStack
.pop_back();
1699 bool DrawTargetWebgl::RemoveAllClips() {
1700 if (mClipStack
.empty()) {
1703 if (!mSkia
->RemoveAllClips()) {
1706 mClipChanged
= true;
1707 mRefreshClipState
= true;
1712 void DrawTargetWebgl::CopyToFallback(DrawTarget
* aDT
) {
1713 if (RefPtr
<SourceSurface
> snapshot
= Snapshot()) {
1714 aDT
->CopySurface(snapshot
, snapshot
->GetRect(), gfx::IntPoint(0, 0));
1716 aDT
->RemoveAllClips();
1717 for (auto& clipStack
: mClipStack
) {
1718 aDT
->SetTransform(clipStack
.mTransform
);
1719 if (clipStack
.mPath
) {
1720 aDT
->PushClip(clipStack
.mPath
);
1722 aDT
->PushClipRect(clipStack
.mRect
);
1725 aDT
->SetTransform(GetTransform());
1728 // Whether a given composition operator can be mapped to a WebGL blend mode.
1729 static inline bool SupportsDrawOptions(const DrawOptions
& aOptions
) {
1730 switch (aOptions
.mCompositionOp
) {
1731 case CompositionOp::OP_OVER
:
1732 case CompositionOp::OP_ADD
:
1733 case CompositionOp::OP_ATOP
:
1734 case CompositionOp::OP_SOURCE
:
1735 case CompositionOp::OP_CLEAR
:
1742 // Whether a pattern can be mapped to an available WebGL shader.
1743 bool SharedContextWebgl::SupportsPattern(const Pattern
& aPattern
) {
1744 switch (aPattern
.GetType()) {
1745 case PatternType::COLOR
:
1747 case PatternType::SURFACE
: {
1748 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
1749 if (surfacePattern
.mExtendMode
!= ExtendMode::CLAMP
) {
1752 if (surfacePattern
.mSurface
) {
1753 // If the surface is already uploaded to a texture, then just use it.
1754 if (IsCompatibleSurface(surfacePattern
.mSurface
)) {
1758 IntSize size
= surfacePattern
.mSurface
->GetSize();
1759 // The maximum size a surface can be before triggering a fallback to
1760 // software. Bound the maximum surface size by the actual texture size
1762 int32_t maxSize
= int32_t(
1763 std::min(StaticPrefs::gfx_canvas_accelerated_max_surface_size(),
1765 // Check if either of the surface dimensions or the sampling rect,
1766 // if supplied, exceed the maximum.
1767 if (std::max(size
.width
, size
.height
) > maxSize
&&
1768 (surfacePattern
.mSamplingRect
.IsEmpty() ||
1769 std::max(surfacePattern
.mSamplingRect
.width
,
1770 surfacePattern
.mSamplingRect
.height
) > maxSize
)) {
1777 // Patterns other than colors and surfaces are currently not accelerated.
1782 bool DrawTargetWebgl::DrawRect(const Rect
& aRect
, const Pattern
& aPattern
,
1783 const DrawOptions
& aOptions
,
1784 Maybe
<DeviceColor
> aMaskColor
,
1785 RefPtr
<TextureHandle
>* aHandle
,
1786 bool aTransformed
, bool aClipped
,
1787 bool aAccelOnly
, bool aForceUpdate
,
1788 const StrokeOptions
* aStrokeOptions
) {
1789 // If there is nothing to draw, then don't draw...
1790 if (aRect
.IsEmpty()) {
1794 // If we're already drawing directly to the WebGL context, then we want to
1795 // continue to do so. However, if we're drawing into a Skia layer over the
1796 // WebGL context, then we need to be careful to avoid repeatedly clearing
1797 // and flushing the layer if we hit a drawing request that can be accelerated
1798 // in between layered drawing requests, as clearing and flushing the layer
1799 // can be significantly expensive when repeated. So when a Skia layer is
1800 // active, if it is possible to continue drawing into the layer, then don't
1801 // accelerate the drawing request.
1802 if (mWebglValid
|| (mSkiaLayer
&& !mLayerDepth
&&
1803 (aAccelOnly
|| !SupportsLayering(aOptions
)))) {
1804 // If we get here, either the WebGL context is being directly drawn to
1805 // or we are going to flush the Skia layer to it before doing so. The shared
1806 // context still needs to be claimed and prepared for drawing. If this
1807 // fails, we just fall back to drawing with Skia below.
1808 if (PrepareContext(aClipped
)) {
1809 // The shared context is claimed and the framebuffer is now valid, so try
1810 // accelerated drawing.
1811 return mSharedContext
->DrawRectAccel(
1812 aRect
, aPattern
, aOptions
, aMaskColor
, aHandle
, aTransformed
,
1813 aClipped
, aAccelOnly
, aForceUpdate
, aStrokeOptions
);
1817 // Either there is no valid WebGL target to draw into, or we failed to prepare
1818 // it for drawing. The only thing we can do at this point is fall back to
1819 // drawing with Skia. If the request explicitly requires accelerated drawing,
1820 // then draw nothing before returning failure.
1822 DrawRectFallback(aRect
, aPattern
, aOptions
, aMaskColor
, aTransformed
,
1823 aClipped
, aStrokeOptions
);
1828 void DrawTargetWebgl::DrawRectFallback(const Rect
& aRect
,
1829 const Pattern
& aPattern
,
1830 const DrawOptions
& aOptions
,
1831 Maybe
<DeviceColor
> aMaskColor
,
1832 bool aTransformed
, bool aClipped
,
1833 const StrokeOptions
* aStrokeOptions
) {
1834 // Invalidate the WebGL target and prepare the Skia target for drawing.
1835 MarkSkiaChanged(aOptions
);
1838 // If transforms are requested, then just translate back to FillRect.
1840 mSkia
->Mask(ColorPattern(*aMaskColor
), aPattern
, aOptions
);
1841 } else if (aStrokeOptions
) {
1842 mSkia
->StrokeRect(aRect
, aPattern
, *aStrokeOptions
, aOptions
);
1844 mSkia
->FillRect(aRect
, aPattern
, aOptions
);
1846 } else if (aClipped
) {
1847 // If no transform was requested but clipping is still required, then
1848 // temporarily reset the transform before translating to FillRect.
1849 mSkia
->SetTransform(Matrix());
1851 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
1852 if (surfacePattern
.mSamplingRect
.IsEmpty()) {
1853 mSkia
->MaskSurface(ColorPattern(*aMaskColor
), surfacePattern
.mSurface
,
1854 aRect
.TopLeft(), aOptions
);
1856 mSkia
->Mask(ColorPattern(*aMaskColor
), aPattern
, aOptions
);
1858 } else if (aStrokeOptions
) {
1859 mSkia
->StrokeRect(aRect
, aPattern
, *aStrokeOptions
, aOptions
);
1861 mSkia
->FillRect(aRect
, aPattern
, aOptions
);
1863 mSkia
->SetTransform(mTransform
);
1864 } else if (aPattern
.GetType() == PatternType::SURFACE
) {
1865 // No transform nor clipping was requested, so it is essentially just a
1867 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
1868 mSkia
->CopySurface(surfacePattern
.mSurface
,
1869 surfacePattern
.mSurface
->GetRect(),
1870 IntPoint::Round(aRect
.TopLeft()));
1876 inline already_AddRefed
<WebGLTexture
> SharedContextWebgl::GetCompatibleSnapshot(
1877 SourceSurface
* aSurface
) const {
1878 if (aSurface
->GetType() == SurfaceType::WEBGL
) {
1879 RefPtr
<SourceSurfaceWebgl
> webglSurf
=
1880 static_cast<SourceSurfaceWebgl
*>(aSurface
);
1881 if (this == webglSurf
->mSharedContext
) {
1882 // If there is a snapshot copy in a texture handle, use that.
1883 if (webglSurf
->mHandle
) {
1885 webglSurf
->mHandle
->GetBackingTexture()->GetWebGLTexture());
1887 if (RefPtr
<DrawTargetWebgl
> webglDT
= webglSurf
->GetTarget()) {
1888 // If there is a copy-on-write reference to a target, use its backing
1889 // texture directly. This is only safe if the targets don't match, but
1890 // MarkChanged should ensure that any snapshots were copied into a
1891 // texture handle before we ever get here.
1892 if (!IsCurrentTarget(webglDT
)) {
1893 return do_AddRef(webglDT
->mTex
);
1901 inline bool SharedContextWebgl::IsCompatibleSurface(
1902 SourceSurface
* aSurface
) const {
1903 return bool(RefPtr
<WebGLTexture
>(GetCompatibleSnapshot(aSurface
)));
1906 bool SharedContextWebgl::UploadSurface(DataSourceSurface
* aData
,
1907 SurfaceFormat aFormat
,
1908 const IntRect
& aSrcRect
,
1909 const IntPoint
& aDstOffset
, bool aInit
,
1911 const RefPtr
<WebGLTexture
>& aTex
) {
1912 webgl::TexUnpackBlobDesc texDesc
= {
1913 LOCAL_GL_TEXTURE_2D
,
1914 {uint32_t(aSrcRect
.width
), uint32_t(aSrcRect
.height
), 1}};
1916 // The surface needs to be uploaded to its backing texture either to
1917 // initialize or update the texture handle contents. Map the data
1918 // contents of the surface so it can be read.
1919 DataSourceSurface::ScopedMap
map(aData
, DataSourceSurface::READ
);
1920 if (!map
.IsMapped()) {
1923 int32_t stride
= map
.GetStride();
1924 int32_t bpp
= BytesPerPixel(aFormat
);
1925 // Get the data pointer range considering the sampling rect offset and
1927 Span
<const uint8_t> range(
1928 map
.GetData() + aSrcRect
.y
* size_t(stride
) + aSrcRect
.x
* bpp
,
1929 std::max(aSrcRect
.height
- 1, 0) * size_t(stride
) +
1930 aSrcRect
.width
* bpp
);
1931 texDesc
.cpuData
= Some(range
);
1932 // If the stride happens to be 4 byte aligned, assume that is the
1933 // desired alignment regardless of format (even A8). Otherwise, we
1934 // default to byte alignment.
1935 texDesc
.unpacking
.alignmentInTypeElems
= stride
% 4 ? 1 : 4;
1936 texDesc
.unpacking
.rowLength
= stride
/ bpp
;
1938 // Create a PBO filled with zero data to initialize the texture data and
1939 // avoid slow initialization inside WebGL.
1940 MOZ_ASSERT(aSrcRect
.TopLeft() == IntPoint(0, 0));
1942 size_t(GetAlignedStride
<4>(aSrcRect
.width
, BytesPerPixel(aFormat
))) *
1944 if (!mZeroBuffer
|| size
> mZeroSize
) {
1945 mZeroBuffer
= mWebgl
->CreateBuffer();
1947 mWebgl
->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER
, mZeroBuffer
);
1948 // WebGL will zero initialize the empty buffer, so we don't send zero data
1950 mWebgl
->BufferData(LOCAL_GL_PIXEL_UNPACK_BUFFER
, size
, nullptr,
1951 LOCAL_GL_STATIC_DRAW
);
1953 mWebgl
->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER
, mZeroBuffer
);
1955 texDesc
.pboOffset
= Some(0);
1957 // Upload as RGBA8 to avoid swizzling during upload. Surfaces provide
1958 // data as BGRA, but we manually swizzle that in the shader. An A8
1959 // surface will be stored as an R8 texture that will also be swizzled
1962 aFormat
== SurfaceFormat::A8
? LOCAL_GL_R8
: LOCAL_GL_RGBA8
;
1964 aFormat
== SurfaceFormat::A8
? LOCAL_GL_RED
: LOCAL_GL_RGBA
;
1965 webgl::PackingInfo texPI
= {extFormat
, LOCAL_GL_UNSIGNED_BYTE
};
1966 // Do the (partial) upload for the shared or standalone texture.
1968 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, aTex
);
1970 mWebgl
->TexImage(0, aInit
? intFormat
: 0,
1971 {uint32_t(aDstOffset
.x
), uint32_t(aDstOffset
.y
), 0}, texPI
,
1974 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mLastTexture
);
1976 if (!aData
&& aZero
) {
1977 mWebgl
->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER
, 0);
1982 // Allocate a new texture handle backed by either a standalone texture or as a
1983 // sub-texture of a larger shared texture.
1984 already_AddRefed
<TextureHandle
> SharedContextWebgl::AllocateTextureHandle(
1985 SurfaceFormat aFormat
, const IntSize
& aSize
, bool aAllowShared
,
1987 RefPtr
<TextureHandle
> handle
;
1988 // Calculate the bytes that would be used by this texture handle, and prune
1989 // enough other textures to ensure we have that much usable texture space
1990 // available to allocate.
1991 size_t usedBytes
= BackingTexture::UsedBytes(aFormat
, aSize
);
1992 PruneTextureMemory(usedBytes
, false);
1993 // The requested page size for shared textures.
1994 int32_t pageSize
= int32_t(std::min(
1995 StaticPrefs::gfx_canvas_accelerated_shared_page_size(), mMaxTextureSize
));
1996 if (aAllowShared
&& std::max(aSize
.width
, aSize
.height
) <= pageSize
/ 2) {
1997 // Ensure that the surface is no bigger than a quadrant of a shared texture
1998 // page. If so, try to allocate it to a shared texture. Look for any
1999 // existing shared texture page with a matching format and allocate
2000 // from that if possible.
2001 for (auto& shared
: mSharedTextures
) {
2002 if (shared
->GetFormat() == aFormat
&&
2003 shared
->IsRenderable() == aRenderable
) {
2004 bool wasEmpty
= !shared
->HasAllocatedHandles();
2005 handle
= shared
->Allocate(aSize
);
2008 // If the page was previously empty, then deduct it from the
2009 // empty memory reserves.
2010 mEmptyTextureMemory
-= shared
->UsedBytes();
2016 // If we couldn't find an existing shared texture page with matching
2017 // format, then allocate a new page to put the request in.
2019 if (RefPtr
<WebGLTexture
> tex
= mWebgl
->CreateTexture()) {
2020 RefPtr
<SharedTexture
> shared
=
2021 new SharedTexture(IntSize(pageSize
, pageSize
), aFormat
, tex
);
2023 shared
->MarkRenderable();
2025 mSharedTextures
.push_back(shared
);
2026 mTotalTextureMemory
+= shared
->UsedBytes();
2027 handle
= shared
->Allocate(aSize
);
2031 // The surface wouldn't fit in a shared texture page, so we need to
2032 // allocate a standalone texture for it instead.
2033 if (RefPtr
<WebGLTexture
> tex
= mWebgl
->CreateTexture()) {
2034 RefPtr
<StandaloneTexture
> standalone
=
2035 new StandaloneTexture(aSize
, aFormat
, tex
);
2037 standalone
->MarkRenderable();
2039 mStandaloneTextures
.push_back(standalone
);
2040 mTotalTextureMemory
+= standalone
->UsedBytes();
2041 handle
= standalone
;
2049 // Insert the new texture handle into the front of the MRU list and
2050 // update used space for it.
2051 mTextureHandles
.insertFront(handle
);
2052 ++mNumTextureHandles
;
2053 mUsedTextureMemory
+= handle
->UsedBytes();
2055 return handle
.forget();
2058 static inline SamplingFilter
GetSamplingFilter(const Pattern
& aPattern
) {
2059 return aPattern
.GetType() == PatternType::SURFACE
2060 ? static_cast<const SurfacePattern
&>(aPattern
).mSamplingFilter
2061 : SamplingFilter::GOOD
;
2064 static inline bool UseNearestFilter(const Pattern
& aPattern
) {
2065 return GetSamplingFilter(aPattern
) == SamplingFilter::POINT
;
2068 // Determine if the rectangle is still axis-aligned and pixel-aligned.
2069 static inline Maybe
<IntRect
> IsAlignedRect(bool aTransformed
,
2070 const Matrix
& aCurrentTransform
,
2071 const Rect
& aRect
) {
2072 if (!aTransformed
|| aCurrentTransform
.HasOnlyIntegerTranslation()) {
2073 auto intRect
= RoundedToInt(aRect
);
2074 if (aRect
.WithinEpsilonOf(Rect(intRect
), 1.0e-3f
)) {
2076 intRect
+= RoundedToInt(aCurrentTransform
.GetTranslation());
2078 return Some(intRect
);
2084 Maybe
<uint32_t> SharedContextWebgl::GetUniformLocation(
2085 const RefPtr
<WebGLProgram
>& aProg
, const std::string
& aName
) const {
2086 if (!aProg
|| !aProg
->LinkInfo()) {
2090 for (const auto& activeUniform
: aProg
->LinkInfo()->active
.activeUniforms
) {
2091 if (activeUniform
.block_index
!= -1) continue;
2093 auto locName
= activeUniform
.name
;
2094 const auto indexed
= webgl::ParseIndexed(locName
);
2096 locName
= indexed
->name
;
2099 const auto baseLength
= locName
.size();
2100 for (const auto& pair
: activeUniform
.locByIndex
) {
2102 locName
.erase(baseLength
); // Erase previous "[N]".
2104 locName
+= std::to_string(pair
.first
);
2107 if (locName
== aName
|| locName
== aName
+ "[0]") {
2108 return Some(pair
.second
);
2117 struct IsUniformDataValT
: std::false_type
{};
2119 struct IsUniformDataValT
<webgl::UniformDataVal
> : std::true_type
{};
2121 struct IsUniformDataValT
<float> : std::true_type
{};
2123 struct IsUniformDataValT
<int32_t> : std::true_type
{};
2125 struct IsUniformDataValT
<uint32_t> : std::true_type
{};
2127 template <typename T
, typename
= std::enable_if_t
<IsUniformDataValT
<T
>::value
>>
2128 inline Range
<const webgl::UniformDataVal
> AsUniformDataVal(
2129 const Range
<const T
>& data
) {
2130 return {data
.begin().template ReinterpretCast
<const webgl::UniformDataVal
>(),
2131 data
.end().template ReinterpretCast
<const webgl::UniformDataVal
>()};
2134 template <class T
, size_t N
>
2135 inline void SharedContextWebgl::UniformData(GLenum aFuncElemType
,
2136 const Maybe
<uint32_t>& aLoc
,
2137 const Array
<T
, N
>& aData
) {
2138 // We currently always pass false for transpose. If in the future we need
2139 // support for transpose then caching needs to take that in to account.
2140 mWebgl
->UniformData(*aLoc
, false,
2141 AsUniformDataVal(Range
<const T
>(Span
<const T
>(aData
))));
2144 template <class T
, size_t N
>
2145 void SharedContextWebgl::MaybeUniformData(GLenum aFuncElemType
,
2146 const Maybe
<uint32_t>& aLoc
,
2147 const Array
<T
, N
>& aData
,
2148 Maybe
<Array
<T
, N
>>& aCached
) {
2149 if (aCached
.isNothing() || !(*aCached
== aData
)) {
2150 aCached
= Some(aData
);
2151 UniformData(aFuncElemType
, aLoc
, aData
);
2155 inline void SharedContextWebgl::DrawQuad() {
2156 mWebgl
->DrawArraysInstanced(LOCAL_GL_TRIANGLE_FAN
, 0, 4, 1);
2159 void SharedContextWebgl::DrawTriangles(const PathVertexRange
& aRange
) {
2160 mWebgl
->DrawArraysInstanced(LOCAL_GL_TRIANGLES
, GLint(aRange
.mOffset
),
2161 GLsizei(aRange
.mLength
), 1);
2164 // Common rectangle and pattern drawing function shared by many DrawTarget
2165 // commands. If aMaskColor is specified, the provided surface pattern will be
2166 // treated as a mask. If aHandle is specified, then the surface pattern's
2167 // texture will be cached in the supplied handle, as opposed to using the
2168 // surface's user data. If aTransformed or aClipped are false, then transforms
2169 // and/or clipping will be disabled. If aAccelOnly is specified, then this
2170 // function will return before it would have otherwise drawn without
2171 // acceleration. If aForceUpdate is specified, then the provided texture handle
2172 // will be respecified with the provided surface.
2173 bool SharedContextWebgl::DrawRectAccel(
2174 const Rect
& aRect
, const Pattern
& aPattern
, const DrawOptions
& aOptions
,
2175 Maybe
<DeviceColor
> aMaskColor
, RefPtr
<TextureHandle
>* aHandle
,
2176 bool aTransformed
, bool aClipped
, bool aAccelOnly
, bool aForceUpdate
,
2177 const StrokeOptions
* aStrokeOptions
, const PathVertexRange
* aVertexRange
,
2178 const Matrix
* aRectXform
) {
2179 // If the rect or clip rect is empty, then there is nothing to draw.
2180 if (aRect
.IsEmpty() || mClipRect
.IsEmpty()) {
2184 // Check if the drawing options and the pattern support acceleration. Also
2185 // ensure the framebuffer is prepared for drawing. If not, fall back to using
2186 // the Skia target. When we need to forcefully update a texture, we must be
2187 // careful to override any pattern limits, as the caller ensures the pattern
2188 // is otherwise a supported type.
2189 if (!SupportsDrawOptions(aOptions
) ||
2190 (!aForceUpdate
&& !SupportsPattern(aPattern
)) || aStrokeOptions
||
2191 !mCurrentTarget
->MarkChanged()) {
2192 // If only accelerated drawing was requested, bail out without software
2193 // drawing fallback.
2195 MOZ_ASSERT(!aVertexRange
);
2196 mCurrentTarget
->DrawRectFallback(aRect
, aPattern
, aOptions
, aMaskColor
,
2197 aTransformed
, aClipped
, aStrokeOptions
);
2202 const Matrix
& currentTransform
= mCurrentTarget
->GetTransform();
2203 // rectXform only applies to the rect, but should not apply to the pattern,
2204 // as it might inadvertently alter the pattern.
2205 Matrix rectXform
= currentTransform
;
2207 rectXform
.PreMultiply(*aRectXform
);
2210 if (aOptions
.mCompositionOp
== CompositionOp::OP_SOURCE
&& aTransformed
&&
2212 (HasClipMask() || !rectXform
.PreservesAxisAlignedRectangles() ||
2213 !rectXform
.TransformBounds(aRect
).Contains(Rect(mClipAARect
)) ||
2214 (aPattern
.GetType() == PatternType::SURFACE
&&
2215 !IsAlignedRect(aTransformed
, rectXform
, aRect
)))) {
2216 // Clear outside the mask region for masks that are not bounded by clip.
2217 return DrawRectAccel(Rect(mClipRect
), ColorPattern(DeviceColor(0, 0, 0, 0)),
2218 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
,
2219 aOptions
.mAntialiasMode
),
2220 Nothing(), nullptr, false, aClipped
, aAccelOnly
) &&
2221 DrawRectAccel(aRect
, aPattern
,
2222 DrawOptions(aOptions
.mAlpha
, CompositionOp::OP_ADD
,
2223 aOptions
.mAntialiasMode
),
2224 aMaskColor
, aHandle
, aTransformed
, aClipped
,
2225 aAccelOnly
, aForceUpdate
, aStrokeOptions
, aVertexRange
,
2228 if (aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
&&
2229 aPattern
.GetType() == PatternType::SURFACE
&& !aMaskColor
) {
2230 // If the surface being drawn with clear is not a mask, then its contents
2231 // needs to be ignored. Just use a color pattern instead.
2232 return DrawRectAccel(aRect
, ColorPattern(DeviceColor(1, 1, 1, 1)), aOptions
,
2233 Nothing(), aHandle
, aTransformed
, aClipped
, aAccelOnly
,
2234 aForceUpdate
, aStrokeOptions
, aVertexRange
,
2238 // Set up the scissor test to reflect the clipping rectangle, if supplied.
2239 if (!mClipRect
.Contains(IntRect(IntPoint(), mViewportSize
))) {
2240 EnableScissor(mClipRect
);
2245 bool success
= false;
2247 // Now try to actually draw the pattern...
2248 switch (aPattern
.GetType()) {
2249 case PatternType::COLOR
: {
2250 if (!aVertexRange
) {
2251 // Only an uncached draw if not using the vertex cache.
2252 mCurrentTarget
->mProfile
.OnUncachedDraw();
2254 DeviceColor color
= PremultiplyColor(
2255 static_cast<const ColorPattern
&>(aPattern
).mColor
, aOptions
.mAlpha
);
2256 if (((color
.a
== 1.0f
&&
2257 aOptions
.mCompositionOp
== CompositionOp::OP_OVER
) ||
2258 aOptions
.mCompositionOp
== CompositionOp::OP_SOURCE
||
2259 aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
) &&
2260 !aStrokeOptions
&& !aVertexRange
&& !HasClipMask() &&
2261 mClipAARect
.IsEqualEdges(Rect(mClipRect
))) {
2262 // Certain color patterns can be mapped to scissored clears. The
2263 // composition op must effectively overwrite the destination, and the
2264 // transform must map to an axis-aligned integer rectangle.
2265 if (Maybe
<IntRect
> intRect
=
2266 IsAlignedRect(aTransformed
, rectXform
, aRect
)) {
2267 // Only use a clear if the area is larger than a quarter or the
2269 if (intRect
->Area() >=
2270 (mViewportSize
.width
/ 2) * (mViewportSize
.height
/ 2)) {
2271 if (!intRect
->Contains(mClipRect
)) {
2272 EnableScissor(intRect
->Intersect(mClipRect
));
2274 if (aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
) {
2276 PremultiplyColor(mCurrentTarget
->GetClearPattern().mColor
);
2278 mWebgl
->ClearColor(color
.b
, color
.g
, color
.r
, color
.a
);
2279 mWebgl
->Clear(LOCAL_GL_COLOR_BUFFER_BIT
);
2285 // Map the composition op to a WebGL blend mode, if possible.
2286 Maybe
<DeviceColor
> blendColor
;
2287 if (aOptions
.mCompositionOp
== CompositionOp::OP_SOURCE
||
2288 aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
) {
2289 // The source operator can support clipping and AA by emulating it with
2290 // the over op. Supply the color with blend state, and set the shader
2291 // color to white, to avoid needing dual-source blending.
2292 blendColor
= Some(color
);
2293 // Both source and clear operators should output a mask from the shader.
2294 color
= DeviceColor(1, 1, 1, 1);
2296 SetBlendState(aOptions
.mCompositionOp
, blendColor
);
2297 // Since it couldn't be mapped to a scissored clear, we need to use the
2298 // solid color shader with supplied transform.
2299 if (mLastProgram
!= mSolidProgram
) {
2300 mWebgl
->UseProgram(mSolidProgram
);
2301 mLastProgram
= mSolidProgram
;
2303 Array
<float, 2> viewportData
= {float(mViewportSize
.width
),
2304 float(mViewportSize
.height
)};
2305 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramViewport
, viewportData
,
2306 mSolidProgramUniformState
.mViewport
);
2308 // Generated paths provide their own AA as vertex alpha.
2309 Array
<float, 1> aaData
= {aVertexRange
? 0.0f
: 1.0f
};
2310 MaybeUniformData(LOCAL_GL_FLOAT
, mSolidProgramAA
, aaData
,
2311 mSolidProgramUniformState
.mAA
);
2313 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel
2315 Array
<float, 4> clipData
= {mClipAARect
.x
- 0.5f
, mClipAARect
.y
- 0.5f
,
2316 mClipAARect
.XMost() + 0.5f
,
2317 mClipAARect
.YMost() + 0.5f
};
2318 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramClipBounds
, clipData
,
2319 mSolidProgramUniformState
.mClipBounds
);
2321 Array
<float, 4> colorData
= {color
.b
, color
.g
, color
.r
, color
.a
};
2322 Matrix
xform(aRect
.width
, 0.0f
, 0.0f
, aRect
.height
, aRect
.x
, aRect
.y
);
2326 Array
<float, 6> xformData
= {xform
._11
, xform
._12
, xform
._21
,
2327 xform
._22
, xform
._31
, xform
._32
};
2328 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramTransform
, xformData
,
2329 mSolidProgramUniformState
.mTransform
);
2331 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramColor
, colorData
,
2332 mSolidProgramUniformState
.mColor
);
2334 // Finally draw the colored rectangle.
2336 // If there's a vertex range, then we need to draw triangles within from
2337 // generated from a path stored in the path vertex buffer.
2338 DrawTriangles(*aVertexRange
);
2340 // Otherwise we're drawing a simple filled rectangle.
2346 case PatternType::SURFACE
: {
2347 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
2348 // If a texture handle was supplied, or if the surface already has an
2349 // assigned texture handle stashed in its used data, try to use it.
2350 RefPtr
<TextureHandle
> handle
=
2351 aHandle
? aHandle
->get()
2352 : (surfacePattern
.mSurface
2353 ? static_cast<TextureHandle
*>(
2354 surfacePattern
.mSurface
->GetUserData(
2355 &mTextureHandleKey
))
2359 SurfaceFormat format
;
2360 // Check if the found handle is still valid and if its sampling rect
2361 // matches the requested sampling rect.
2362 if (handle
&& handle
->IsValid() &&
2363 (surfacePattern
.mSamplingRect
.IsEmpty() ||
2364 handle
->GetSamplingRect().IsEqualEdges(
2365 surfacePattern
.mSamplingRect
))) {
2366 texSize
= handle
->GetSize();
2367 format
= handle
->GetFormat();
2368 offset
= handle
->GetSamplingOffset();
2370 // Otherwise, there is no handle that can be used yet, so extract
2371 // information from the surface pattern.
2373 if (!surfacePattern
.mSurface
) {
2374 // If there was no actual surface supplied, then we tried to draw
2375 // using a texture handle, but the texture handle wasn't valid.
2378 texSize
= surfacePattern
.mSurface
->GetSize();
2379 format
= surfacePattern
.mSurface
->GetFormat();
2380 if (!surfacePattern
.mSamplingRect
.IsEmpty()) {
2381 texSize
= surfacePattern
.mSamplingRect
.Size();
2382 offset
= surfacePattern
.mSamplingRect
.TopLeft();
2386 // We need to be able to transform from local space into texture space.
2387 Matrix invMatrix
= surfacePattern
.mMatrix
;
2388 // If drawing a pre-transformed vertex range, then we need to ensure the
2389 // user-space pattern is still transformed to screen-space.
2390 if (aVertexRange
&& !aTransformed
) {
2391 invMatrix
*= currentTransform
;
2393 if (!invMatrix
.Invert()) {
2397 // If there is aRectXform, it must be applied to the source rectangle to
2398 // generate the proper input coordinates for the inverse pattern matrix.
2399 invMatrix
.PreMultiply(*aRectXform
);
2402 RefPtr
<WebGLTexture
> tex
;
2404 IntSize backingSize
;
2405 RefPtr
<DataSourceSurface
> data
;
2408 data
= surfacePattern
.mSurface
->GetDataSurface();
2412 // The size of the texture may change if we update contents.
2413 mUsedTextureMemory
-= handle
->UsedBytes();
2414 handle
->UpdateSize(texSize
);
2415 mUsedTextureMemory
+= handle
->UsedBytes();
2416 handle
->SetSamplingOffset(surfacePattern
.mSamplingRect
.TopLeft());
2418 // If using an existing handle, move it to the front of the MRU list.
2420 mTextureHandles
.insertFront(handle
);
2421 } else if ((tex
= GetCompatibleSnapshot(surfacePattern
.mSurface
))) {
2422 backingSize
= surfacePattern
.mSurface
->GetSize();
2423 bounds
= IntRect(offset
, texSize
);
2424 // Count reusing a snapshot texture (no readback) as a cache hit.
2425 mCurrentTarget
->mProfile
.OnCacheHit();
2427 // If we get here, we need a data surface for a texture upload.
2428 data
= surfacePattern
.mSurface
->GetDataSurface();
2432 // There is no existing handle. Try to allocate a new one. If the
2433 // surface size may change via a forced update, then don't allocate
2434 // from a shared texture page.
2435 handle
= AllocateTextureHandle(format
, texSize
, !aForceUpdate
);
2440 // Link the handle to the surface's user data.
2441 handle
->SetSamplingOffset(surfacePattern
.mSamplingRect
.TopLeft());
2445 handle
->SetSurface(surfacePattern
.mSurface
);
2446 surfacePattern
.mSurface
->AddUserData(&mTextureHandleKey
, handle
.get(),
2451 // Map the composition op to a WebGL blend mode, if possible. If there is
2452 // a mask color and a texture with multiple channels, assume subpixel
2453 // blending. If we encounter the source op here, then assume the surface
2454 // is opaque (non-opaque is handled above) and emulate it with over.
2455 SetBlendState(aOptions
.mCompositionOp
,
2456 format
!= SurfaceFormat::A8
? aMaskColor
: Nothing());
2457 // Switch to the image shader and set up relevant transforms.
2458 if (mLastProgram
!= mImageProgram
) {
2459 mWebgl
->UseProgram(mImageProgram
);
2460 mLastProgram
= mImageProgram
;
2463 Array
<float, 2> viewportData
= {float(mViewportSize
.width
),
2464 float(mViewportSize
.height
)};
2465 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mImageProgramViewport
, viewportData
,
2466 mImageProgramUniformState
.mViewport
);
2468 // AA is not supported for OP_SOURCE. Generated paths provide their own
2469 // AA as vertex alpha.
2470 Array
<float, 1> aaData
= {
2471 mLastCompositionOp
== CompositionOp::OP_SOURCE
|| aVertexRange
2474 MaybeUniformData(LOCAL_GL_FLOAT
, mImageProgramAA
, aaData
,
2475 mImageProgramUniformState
.mAA
);
2477 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel
2479 Array
<float, 4> clipData
= {mClipAARect
.x
- 0.5f
, mClipAARect
.y
- 0.5f
,
2480 mClipAARect
.XMost() + 0.5f
,
2481 mClipAARect
.YMost() + 0.5f
};
2482 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mImageProgramClipBounds
, clipData
,
2483 mImageProgramUniformState
.mClipBounds
);
2486 mLastCompositionOp
== CompositionOp::OP_CLEAR
2487 ? DeviceColor(1, 1, 1, 1)
2489 aMaskColor
&& format
!= SurfaceFormat::A8
2490 ? DeviceColor::Mask(1.0f
, aMaskColor
->a
)
2491 : aMaskColor
.valueOr(DeviceColor(1, 1, 1, 1)),
2493 Array
<float, 4> colorData
= {color
.b
, color
.g
, color
.r
, color
.a
};
2494 Array
<float, 1> swizzleData
= {format
== SurfaceFormat::A8
? 1.0f
: 0.0f
};
2495 Matrix
xform(aRect
.width
, 0.0f
, 0.0f
, aRect
.height
, aRect
.x
, aRect
.y
);
2499 Array
<float, 6> xformData
= {xform
._11
, xform
._12
, xform
._21
,
2500 xform
._22
, xform
._31
, xform
._32
};
2501 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mImageProgramTransform
, xformData
,
2502 mImageProgramUniformState
.mTransform
);
2504 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mImageProgramColor
, colorData
,
2505 mImageProgramUniformState
.mColor
);
2507 MaybeUniformData(LOCAL_GL_FLOAT
, mImageProgramSwizzle
, swizzleData
,
2508 mImageProgramUniformState
.mSwizzle
);
2510 // Start binding the WebGL state for the texture.
2511 BackingTexture
* backing
= nullptr;
2513 backing
= handle
->GetBackingTexture();
2515 tex
= backing
->GetWebGLTexture();
2517 bounds
= handle
->GetBounds();
2518 backingSize
= backing
->GetSize();
2520 if (mLastTexture
!= tex
) {
2521 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, tex
);
2525 if (backing
&& !backing
->IsInitialized()) {
2526 // If this is the first time the texture is used, we need to initialize
2527 // the clamping and filtering state.
2528 backing
->MarkInitialized();
2529 InitTexParameters(tex
);
2530 if (texSize
!= backingSize
) {
2531 // If this is a shared texture handle whose actual backing texture is
2532 // larger than it, then we need to allocate the texture page to the
2533 // full backing size before we can do a partial upload of the surface.
2534 UploadSurface(nullptr, format
, IntRect(IntPoint(), backingSize
),
2535 IntPoint(), true, true);
2540 UploadSurface(data
, format
, IntRect(offset
, texSize
), bounds
.TopLeft(),
2541 texSize
== backingSize
);
2542 // Signal that we had to upload new data to the texture cache.
2543 mCurrentTarget
->mProfile
.OnCacheMiss();
2545 // Signal that we are reusing data from the texture cache.
2546 mCurrentTarget
->mProfile
.OnCacheHit();
2549 // Set up the texture coordinate matrix to map from the input rectangle to
2550 // the backing texture subrect.
2551 Size
backingSizeF(backingSize
);
2552 Matrix
uvMatrix(aRect
.width
, 0.0f
, 0.0f
, aRect
.height
, aRect
.x
, aRect
.y
);
2553 uvMatrix
*= invMatrix
;
2554 uvMatrix
*= Matrix(1.0f
/ backingSizeF
.width
, 0.0f
, 0.0f
,
2555 1.0f
/ backingSizeF
.height
,
2556 float(bounds
.x
- offset
.x
) / backingSizeF
.width
,
2557 float(bounds
.y
- offset
.y
) / backingSizeF
.height
);
2558 Array
<float, 6> uvData
= {uvMatrix
._11
, uvMatrix
._12
, uvMatrix
._21
,
2559 uvMatrix
._22
, uvMatrix
._31
, uvMatrix
._32
};
2560 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mImageProgramTexMatrix
, uvData
,
2561 mImageProgramUniformState
.mTexMatrix
);
2563 // Clamp sampling to within the bounds of the backing texture subrect.
2564 Array
<float, 4> texBounds
= {
2565 (bounds
.x
+ 0.5f
) / backingSizeF
.width
,
2566 (bounds
.y
+ 0.5f
) / backingSizeF
.height
,
2567 (bounds
.XMost() - 0.5f
) / backingSizeF
.width
,
2568 (bounds
.YMost() - 0.5f
) / backingSizeF
.height
,
2570 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mImageProgramTexBounds
, texBounds
,
2571 mImageProgramUniformState
.mTexBounds
);
2573 // Ensure we use nearest filtering when no antialiasing is requested.
2574 if (UseNearestFilter(surfacePattern
)) {
2575 SetTexFilter(tex
, false);
2578 // Finally draw the image rectangle.
2580 // If there's a vertex range, then we need to draw triangles within from
2581 // generated from a path stored in the path vertex buffer.
2582 DrawTriangles(*aVertexRange
);
2584 // Otherwise we're drawing a simple filled rectangle.
2588 // Restore the default linear filter if overridden.
2589 if (UseNearestFilter(surfacePattern
)) {
2590 SetTexFilter(tex
, true);
2597 gfxWarning() << "Unknown DrawTargetWebgl::DrawRect pattern type: "
2598 << (int)aPattern
.GetType();
2605 bool SharedContextWebgl::RemoveSharedTexture(
2606 const RefPtr
<SharedTexture
>& aTexture
) {
2608 std::find(mSharedTextures
.begin(), mSharedTextures
.end(), aTexture
);
2609 if (pos
== mSharedTextures
.end()) {
2612 // Keep around a reserve of empty pages to avoid initialization costs from
2613 // allocating shared pages. If still below the limit of reserved pages, then
2614 // just add it to the reserve. Otherwise, erase the empty texture page.
2615 size_t maxBytes
= StaticPrefs::gfx_canvas_accelerated_reserve_empty_cache()
2617 size_t usedBytes
= aTexture
->UsedBytes();
2618 if (mEmptyTextureMemory
+ usedBytes
<= maxBytes
) {
2619 mEmptyTextureMemory
+= usedBytes
;
2621 mTotalTextureMemory
-= usedBytes
;
2622 mSharedTextures
.erase(pos
);
2628 void SharedTextureHandle::Cleanup(SharedContextWebgl
& aContext
) {
2629 mTexture
->Free(*this);
2631 // Check if the shared handle's owning page has no more allocated handles
2632 // after we freed it. If so, remove the empty shared texture page also.
2633 if (!mTexture
->HasAllocatedHandles()) {
2634 aContext
.RemoveSharedTexture(mTexture
);
2638 bool SharedContextWebgl::RemoveStandaloneTexture(
2639 const RefPtr
<StandaloneTexture
>& aTexture
) {
2640 auto pos
= std::find(mStandaloneTextures
.begin(), mStandaloneTextures
.end(),
2642 if (pos
== mStandaloneTextures
.end()) {
2645 mTotalTextureMemory
-= aTexture
->UsedBytes();
2646 mStandaloneTextures
.erase(pos
);
2651 void StandaloneTexture::Cleanup(SharedContextWebgl
& aContext
) {
2652 aContext
.RemoveStandaloneTexture(this);
2655 // Prune a given texture handle and release its associated resources.
2656 void SharedContextWebgl::PruneTextureHandle(
2657 const RefPtr
<TextureHandle
>& aHandle
) {
2658 // Invalidate the handle so nothing will subsequently use its contents.
2659 aHandle
->Invalidate();
2660 // If the handle has an associated SourceSurface, unlink it.
2661 UnlinkSurfaceTexture(aHandle
);
2662 // If the handle has an associated CacheEntry, unlink it.
2663 if (RefPtr
<CacheEntry
> entry
= aHandle
->GetCacheEntry()) {
2666 // Deduct the used space from the total.
2667 mUsedTextureMemory
-= aHandle
->UsedBytes();
2668 // Ensure any allocated shared or standalone texture regions get freed.
2669 aHandle
->Cleanup(*this);
2672 // Prune any texture memory above the limit (or margin below the limit) or any
2673 // least-recently-used handles that are no longer associated with any usable
2675 bool SharedContextWebgl::PruneTextureMemory(size_t aMargin
, bool aPruneUnused
) {
2676 // The maximum amount of texture memory that may be used by textures.
2677 size_t maxBytes
= StaticPrefs::gfx_canvas_accelerated_cache_size() << 20;
2678 maxBytes
-= std::min(maxBytes
, aMargin
);
2679 size_t maxItems
= StaticPrefs::gfx_canvas_accelerated_cache_items();
2680 size_t oldItems
= mNumTextureHandles
;
2681 while (!mTextureHandles
.isEmpty() &&
2682 (mUsedTextureMemory
> maxBytes
|| mNumTextureHandles
> maxItems
||
2683 (aPruneUnused
&& !mTextureHandles
.getLast()->IsUsed()))) {
2684 PruneTextureHandle(mTextureHandles
.popLast());
2685 --mNumTextureHandles
;
2687 return mNumTextureHandles
< oldItems
;
2690 void DrawTargetWebgl::FillRect(const Rect
& aRect
, const Pattern
& aPattern
,
2691 const DrawOptions
& aOptions
) {
2692 if (SupportsPattern(aPattern
)) {
2693 if (RectInsidePrecisionLimits(aRect
, mTransform
)) {
2694 DrawRect(aRect
, aPattern
, aOptions
);
2697 if (aPattern
.GetType() == PatternType::COLOR
&&
2698 RectContainsViewport(aRect
)) {
2699 // If the pattern is transform-invariant and the rect encompasses the
2700 // entire viewport, just clip drawing to the viewport to avoid transform
2702 DrawRect(Rect(GetRect()), aPattern
, aOptions
, Nothing(), nullptr, false);
2707 MarkSkiaChanged(aOptions
);
2708 mSkia
->FillRect(aRect
, aPattern
, aOptions
);
2710 // If the pattern is unsupported, then transform the rect to a path so it
2713 skiaPath
.addRect(RectToSkRect(aRect
));
2714 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
2715 DrawPath(path
, aPattern
, aOptions
);
2719 void CacheEntry::Link(const RefPtr
<TextureHandle
>& aHandle
) {
2721 mHandle
->SetCacheEntry(this);
2724 // When the CacheEntry becomes unused, it marks the corresponding
2725 // TextureHandle as unused and unlinks it from the CacheEntry. The
2726 // entry is removed from its containing Cache, if applicable.
2727 void CacheEntry::Unlink() {
2728 // The entry may not have a valid handle if rasterization failed.
2730 mHandle
->SetCacheEntry(nullptr);
2737 // Hashes a path and pattern to a single hash value that can be used for quick
2738 // comparisons. This currently avoids to expensive hashing of internal path
2739 // and pattern data for speed, relying instead on later exact comparisons for
2741 HashNumber
PathCacheEntry::HashPath(const QuantizedPath
& aPath
,
2742 const Pattern
* aPattern
,
2743 const Matrix
& aTransform
,
2744 const IntRect
& aBounds
,
2745 const Point
& aOrigin
) {
2746 HashNumber hash
= 0;
2747 hash
= AddToHash(hash
, aPath
.mPath
.num_types
);
2748 hash
= AddToHash(hash
, aPath
.mPath
.num_points
);
2749 if (aPath
.mPath
.num_points
> 0) {
2750 hash
= AddToHash(hash
, aPath
.mPath
.points
[0].x
);
2751 hash
= AddToHash(hash
, aPath
.mPath
.points
[0].y
);
2752 if (aPath
.mPath
.num_points
> 1) {
2753 hash
= AddToHash(hash
, aPath
.mPath
.points
[1].x
);
2754 hash
= AddToHash(hash
, aPath
.mPath
.points
[1].y
);
2757 // Quantize the relative offset of the path to its bounds.
2758 IntPoint offset
= RoundedToInt((aOrigin
- Point(aBounds
.TopLeft())) * 16.0f
);
2759 hash
= AddToHash(hash
, offset
.x
);
2760 hash
= AddToHash(hash
, offset
.y
);
2761 hash
= AddToHash(hash
, aBounds
.width
);
2762 hash
= AddToHash(hash
, aBounds
.height
);
2764 hash
= AddToHash(hash
, (int)aPattern
->GetType());
2769 // When caching rendered geometry, we need to ensure the scale and orientation
2770 // is approximately the same. The offset will be considered separately.
2771 static inline bool HasMatchingScale(const Matrix
& aTransform1
,
2772 const Matrix
& aTransform2
) {
2773 return FuzzyEqual(aTransform1
._11
, aTransform2
._11
) &&
2774 FuzzyEqual(aTransform1
._12
, aTransform2
._12
) &&
2775 FuzzyEqual(aTransform1
._21
, aTransform2
._21
) &&
2776 FuzzyEqual(aTransform1
._22
, aTransform2
._22
);
2779 // Determines if an existing path cache entry matches an incoming path and
2781 inline bool PathCacheEntry::MatchesPath(const QuantizedPath
& aPath
,
2782 const Pattern
* aPattern
,
2783 const StrokeOptions
* aStrokeOptions
,
2784 const Matrix
& aTransform
,
2785 const IntRect
& aBounds
,
2786 const Point
& aOrigin
, HashNumber aHash
,
2788 return aHash
== mHash
&& HasMatchingScale(aTransform
, mTransform
) &&
2789 // Ensure the clipped relative bounds fit inside those of the entry
2790 aBounds
.x
- aOrigin
.x
>= mBounds
.x
- mOrigin
.x
&&
2791 (aBounds
.x
- aOrigin
.x
) + aBounds
.width
<=
2792 (mBounds
.x
- mOrigin
.x
) + mBounds
.width
&&
2793 aBounds
.y
- aOrigin
.y
>= mBounds
.y
- mOrigin
.y
&&
2794 (aBounds
.y
- aOrigin
.y
) + aBounds
.height
<=
2795 (mBounds
.y
- mOrigin
.y
) + mBounds
.height
&&
2797 (!aPattern
? !mPattern
: mPattern
&& *aPattern
== *mPattern
) &&
2800 : mStrokeOptions
&& *aStrokeOptions
== *mStrokeOptions
) &&
2804 PathCacheEntry::PathCacheEntry(QuantizedPath
&& aPath
, Pattern
* aPattern
,
2805 StoredStrokeOptions
* aStrokeOptions
,
2806 const Matrix
& aTransform
, const IntRect
& aBounds
,
2807 const Point
& aOrigin
, HashNumber aHash
,
2809 : CacheEntryImpl
<PathCacheEntry
>(aTransform
, aBounds
, aHash
),
2810 mPath(std::move(aPath
)),
2813 mStrokeOptions(aStrokeOptions
),
2816 // Attempt to find a matching entry in the path cache. If one isn't found,
2817 // a new entry will be created. The caller should check whether the contained
2818 // texture handle is valid to determine if it will need to render the text run
2819 // or just reuse the cached texture.
2820 already_AddRefed
<PathCacheEntry
> PathCache::FindOrInsertEntry(
2821 QuantizedPath aPath
, const Pattern
* aPattern
,
2822 const StrokeOptions
* aStrokeOptions
, const Matrix
& aTransform
,
2823 const IntRect
& aBounds
, const Point
& aOrigin
, float aSigma
) {
2825 PathCacheEntry::HashPath(aPath
, aPattern
, aTransform
, aBounds
, aOrigin
);
2826 for (const RefPtr
<PathCacheEntry
>& entry
: GetChain(hash
)) {
2827 if (entry
->MatchesPath(aPath
, aPattern
, aStrokeOptions
, aTransform
, aBounds
,
2828 aOrigin
, hash
, aSigma
)) {
2829 return do_AddRef(entry
);
2832 Pattern
* pattern
= nullptr;
2834 pattern
= aPattern
->CloneWeak();
2839 StoredStrokeOptions
* strokeOptions
= nullptr;
2840 if (aStrokeOptions
) {
2841 strokeOptions
= aStrokeOptions
->Clone();
2842 if (!strokeOptions
) {
2846 RefPtr
<PathCacheEntry
> entry
=
2847 new PathCacheEntry(std::move(aPath
), pattern
, strokeOptions
, aTransform
,
2848 aBounds
, aOrigin
, hash
, aSigma
);
2850 return entry
.forget();
2853 void DrawTargetWebgl::Fill(const Path
* aPath
, const Pattern
& aPattern
,
2854 const DrawOptions
& aOptions
) {
2855 if (!aPath
|| aPath
->GetBackendType() != BackendType::SKIA
) {
2859 const SkPath
& skiaPath
= static_cast<const PathSkia
*>(aPath
)->GetPath();
2860 SkRect skiaRect
= SkRect::MakeEmpty();
2861 // Draw the path as a simple rectangle with a supported pattern when possible.
2862 if (skiaPath
.isRect(&skiaRect
) && SupportsPattern(aPattern
)) {
2863 Rect rect
= SkRectToRect(skiaRect
);
2864 if (RectInsidePrecisionLimits(rect
, mTransform
)) {
2865 DrawRect(rect
, aPattern
, aOptions
);
2868 if (aPattern
.GetType() == PatternType::COLOR
&&
2869 RectContainsViewport(rect
)) {
2870 // If the pattern is transform-invariant and the rect encompasses the
2871 // entire viewport, just clip drawing to the viewport to avoid transform
2873 DrawRect(Rect(GetRect()), aPattern
, aOptions
, Nothing(), nullptr, false);
2878 DrawPath(aPath
, aPattern
, aOptions
);
2881 void DrawTargetWebgl::FillCircle(const Point
& aOrigin
, float aRadius
,
2882 const Pattern
& aPattern
,
2883 const DrawOptions
& aOptions
) {
2884 DrawCircle(aOrigin
, aRadius
, aPattern
, aOptions
);
2887 QuantizedPath::QuantizedPath(const WGR::Path
& aPath
) : mPath(aPath
) {}
2889 QuantizedPath::QuantizedPath(QuantizedPath
&& aPath
) noexcept
2890 : mPath(aPath
.mPath
) {
2891 aPath
.mPath
.points
= nullptr;
2892 aPath
.mPath
.num_points
= 0;
2893 aPath
.mPath
.types
= nullptr;
2894 aPath
.mPath
.num_types
= 0;
2897 QuantizedPath::~QuantizedPath() {
2898 if (mPath
.points
|| mPath
.types
) {
2899 WGR::wgr_path_release(mPath
);
2903 bool QuantizedPath::operator==(const QuantizedPath
& aOther
) const {
2904 return mPath
.num_types
== aOther
.mPath
.num_types
&&
2905 mPath
.num_points
== aOther
.mPath
.num_points
&&
2906 mPath
.fill_mode
== aOther
.mPath
.fill_mode
&&
2907 !memcmp(mPath
.types
, aOther
.mPath
.types
,
2908 mPath
.num_types
* sizeof(uint8_t)) &&
2909 !memcmp(mPath
.points
, aOther
.mPath
.points
,
2910 mPath
.num_points
* sizeof(WGR::Point
));
2913 // Generate a quantized path from the Skia path using WGR. The supplied
2914 // transform will be applied to the path. The path is stored relative to its
2915 // bounds origin to support translation later.
2916 static Maybe
<QuantizedPath
> GenerateQuantizedPath(
2917 WGR::PathBuilder
* aPathBuilder
, const SkPath
& aPath
, const Rect
& aBounds
,
2918 const Matrix
& aTransform
) {
2919 if (!aPathBuilder
) {
2923 WGR::wgr_builder_reset(aPathBuilder
);
2924 WGR::wgr_builder_set_fill_mode(aPathBuilder
,
2925 aPath
.getFillType() == SkPathFillType::kWinding
2926 ? WGR::FillMode::Winding
2927 : WGR::FillMode::EvenOdd
);
2929 SkPath::RawIter
iter(aPath
);
2931 SkPath::Verb currentVerb
;
2933 // printf_stderr("bounds: (%d, %d) %d x %d\n", aBounds.x, aBounds.y,
2934 // aBounds.width, aBounds.height);
2935 Matrix transform
= aTransform
;
2936 transform
.PostTranslate(-aBounds
.TopLeft());
2937 while ((currentVerb
= iter
.next(params
)) != SkPath::kDone_Verb
) {
2938 switch (currentVerb
) {
2939 case SkPath::kMove_Verb
: {
2940 Point p0
= transform
.TransformPoint(SkPointToPoint(params
[0]));
2941 WGR::wgr_builder_move_to(aPathBuilder
, p0
.x
, p0
.y
);
2944 case SkPath::kLine_Verb
: {
2945 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
2946 WGR::wgr_builder_line_to(aPathBuilder
, p1
.x
, p1
.y
);
2949 case SkPath::kCubic_Verb
: {
2950 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
2951 Point p2
= transform
.TransformPoint(SkPointToPoint(params
[2]));
2952 Point p3
= transform
.TransformPoint(SkPointToPoint(params
[3]));
2953 // printf_stderr("cubic (%f, %f), (%f, %f), (%f, %f)\n", p1.x, p1.y,
2954 // p2.x, p2.y, p3.x, p3.y);
2955 WGR::wgr_builder_curve_to(aPathBuilder
, p1
.x
, p1
.y
, p2
.x
, p2
.y
, p3
.x
,
2959 case SkPath::kQuad_Verb
: {
2960 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
2961 Point p2
= transform
.TransformPoint(SkPointToPoint(params
[2]));
2962 // printf_stderr("quad (%f, %f), (%f, %f)\n", p1.x, p1.y, p2.x, p2.y);
2963 WGR::wgr_builder_quad_to(aPathBuilder
, p1
.x
, p1
.y
, p2
.x
, p2
.y
);
2966 case SkPath::kConic_Verb
: {
2967 Point p0
= transform
.TransformPoint(SkPointToPoint(params
[0]));
2968 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
2969 Point p2
= transform
.TransformPoint(SkPointToPoint(params
[2]));
2970 float w
= iter
.conicWeight();
2971 std::vector
<Point
> quads
;
2972 int numQuads
= ConvertConicToQuads(p0
, p1
, p2
, w
, quads
);
2973 for (int i
= 0; i
< numQuads
; i
++) {
2974 Point q1
= quads
[2 * i
+ 1];
2975 Point q2
= quads
[2 * i
+ 2];
2976 // printf_stderr("conic quad (%f, %f), (%f, %f)\n", q1.x, q1.y, q2.x,
2978 WGR::wgr_builder_quad_to(aPathBuilder
, q1
.x
, q1
.y
, q2
.x
, q2
.y
);
2982 case SkPath::kClose_Verb
:
2983 // printf_stderr("close\n");
2984 WGR::wgr_builder_close(aPathBuilder
);
2988 // Unexpected verb found in path!
2993 WGR::Path p
= WGR::wgr_builder_get_path(aPathBuilder
);
2994 if (!p
.num_points
|| !p
.num_types
) {
2995 WGR::wgr_path_release(p
);
2998 return Some(QuantizedPath(p
));
3001 // Get the output vertex buffer using WGR from an input quantized path.
3002 static Maybe
<WGR::VertexBuffer
> GeneratePathVertexBuffer(
3003 const QuantizedPath
& aPath
, const IntRect
& aClipRect
,
3004 bool aRasterizationTruncates
, WGR::OutputVertex
* aBuffer
,
3005 size_t aBufferCapacity
) {
3006 WGR::VertexBuffer vb
= WGR::wgr_path_rasterize_to_tri_list(
3007 &aPath
.mPath
, aClipRect
.x
, aClipRect
.y
, aClipRect
.width
, aClipRect
.height
,
3008 true, false, aRasterizationTruncates
, aBuffer
, aBufferCapacity
);
3009 if (!vb
.len
|| (aBuffer
&& vb
.len
> aBufferCapacity
)) {
3010 WGR::wgr_vertex_buffer_release(vb
);
3016 static inline AAStroke::LineJoin
ToAAStrokeLineJoin(JoinStyle aJoin
) {
3018 case JoinStyle::BEVEL
:
3019 return AAStroke::LineJoin::Bevel
;
3020 case JoinStyle::ROUND
:
3021 return AAStroke::LineJoin::Round
;
3022 case JoinStyle::MITER
:
3023 case JoinStyle::MITER_OR_BEVEL
:
3024 return AAStroke::LineJoin::Miter
;
3026 return AAStroke::LineJoin::Miter
;
3029 static inline AAStroke::LineCap
ToAAStrokeLineCap(CapStyle aCap
) {
3031 case CapStyle::BUTT
:
3032 return AAStroke::LineCap::Butt
;
3033 case CapStyle::ROUND
:
3034 return AAStroke::LineCap::Round
;
3035 case CapStyle::SQUARE
:
3036 return AAStroke::LineCap::Square
;
3038 return AAStroke::LineCap::Butt
;
3041 static inline Point
WGRPointToPoint(const WGR::Point
& aPoint
) {
3042 return Point(IntPoint(aPoint
.x
, aPoint
.y
)) * (1.0f
/ 16.0f
);
3045 // Generates a vertex buffer for a stroked path using aa-stroke.
3046 static Maybe
<AAStroke::VertexBuffer
> GenerateStrokeVertexBuffer(
3047 const QuantizedPath
& aPath
, const StrokeOptions
* aStrokeOptions
,
3048 float aScale
, WGR::OutputVertex
* aBuffer
, size_t aBufferCapacity
) {
3049 AAStroke::StrokeStyle style
= {aStrokeOptions
->mLineWidth
* aScale
,
3050 ToAAStrokeLineCap(aStrokeOptions
->mLineCap
),
3051 ToAAStrokeLineJoin(aStrokeOptions
->mLineJoin
),
3052 aStrokeOptions
->mMiterLimit
};
3053 if (style
.width
<= 0.0f
|| !std::isfinite(style
.width
) ||
3054 style
.miter_limit
<= 0.0f
|| !std::isfinite(style
.miter_limit
)) {
3057 AAStroke::Stroker
* s
= AAStroke::aa_stroke_new(
3058 &style
, (AAStroke::OutputVertex
*)aBuffer
, aBufferCapacity
);
3060 size_t curPoint
= 0;
3061 for (size_t curType
= 0; valid
&& curType
< aPath
.mPath
.num_types
;) {
3062 // Verify that we are at the start of a sub-path.
3063 if ((aPath
.mPath
.types
[curType
] & WGR::PathPointTypePathTypeMask
) !=
3064 WGR::PathPointTypeStart
) {
3068 // Find where the next sub-path starts so we can locate the end.
3069 size_t endType
= curType
+ 1;
3070 for (; endType
< aPath
.mPath
.num_types
; endType
++) {
3071 if ((aPath
.mPath
.types
[endType
] & WGR::PathPointTypePathTypeMask
) ==
3072 WGR::PathPointTypeStart
) {
3076 // Check if the path is closed. This is a flag modifying the last type.
3078 (aPath
.mPath
.types
[endType
- 1] & WGR::PathPointTypeCloseSubpath
) != 0;
3079 for (; curType
< endType
; curType
++) {
3080 // If this is the last type and the sub-path is not closed, determine if
3081 // this segment should be capped.
3082 bool end
= curType
+ 1 == endType
&& !closed
;
3083 switch (aPath
.mPath
.types
[curType
] & WGR::PathPointTypePathTypeMask
) {
3084 case WGR::PathPointTypeStart
: {
3085 if (curPoint
+ 1 > aPath
.mPath
.num_points
) {
3089 Point p1
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
]);
3090 AAStroke::aa_stroke_move_to(s
, p1
.x
, p1
.y
, closed
);
3092 AAStroke::aa_stroke_line_to(s
, p1
.x
, p1
.y
, true);
3097 case WGR::PathPointTypeLine
: {
3098 if (curPoint
+ 1 > aPath
.mPath
.num_points
) {
3102 Point p1
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
]);
3103 AAStroke::aa_stroke_line_to(s
, p1
.x
, p1
.y
, end
);
3107 case WGR::PathPointTypeBezier
: {
3108 if (curPoint
+ 3 > aPath
.mPath
.num_points
) {
3112 Point p1
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
]);
3113 Point p2
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
+ 1]);
3114 Point p3
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
+ 2]);
3115 AAStroke::aa_stroke_curve_to(s
, p1
.x
, p1
.y
, p2
.x
, p2
.y
, p3
.x
, p3
.y
,
3121 MOZ_ASSERT(false, "Unknown WGR path point type");
3126 // Close the sub-path if necessary.
3127 if (valid
&& closed
) {
3128 AAStroke::aa_stroke_close(s
);
3131 Maybe
<AAStroke::VertexBuffer
> result
;
3133 AAStroke::VertexBuffer vb
= AAStroke::aa_stroke_finish(s
);
3134 if (!vb
.len
|| (aBuffer
&& vb
.len
> aBufferCapacity
)) {
3135 AAStroke::aa_stroke_vertex_buffer_release(vb
);
3140 AAStroke::aa_stroke_release(s
);
3144 // Search the path cache for any entries stored in the path vertex buffer and
3146 void PathCache::ClearVertexRanges() {
3147 for (auto& chain
: mChains
) {
3148 PathCacheEntry
* entry
= chain
.getFirst();
3150 PathCacheEntry
* next
= entry
->getNext();
3151 if (entry
->GetVertexRange().IsValid()) {
3159 inline bool DrawTargetWebgl::ShouldAccelPath(
3160 const DrawOptions
& aOptions
, const StrokeOptions
* aStrokeOptions
) {
3161 return mWebglValid
&& SupportsDrawOptions(aOptions
) && PrepareContext();
3164 enum class AAStrokeMode
{
3170 // For now, we only directly support stroking solid color patterns to limit
3171 // artifacts from blending of overlapping geometry generated by AAStroke. Other
3172 // types of patterns may be partially supported by rendering to a temporary
3174 static inline AAStrokeMode
SupportsAAStroke(const Pattern
& aPattern
,
3175 const DrawOptions
& aOptions
,
3176 const StrokeOptions
& aStrokeOptions
,
3177 bool aAllowStrokeAlpha
) {
3178 if (aStrokeOptions
.mDashPattern
) {
3179 return AAStrokeMode::Unsupported
;
3181 switch (aOptions
.mCompositionOp
) {
3182 case CompositionOp::OP_SOURCE
:
3183 return AAStrokeMode::Geometry
;
3184 case CompositionOp::OP_OVER
:
3185 if (aPattern
.GetType() == PatternType::COLOR
) {
3186 return static_cast<const ColorPattern
&>(aPattern
).mColor
.a
*
3190 ? AAStrokeMode::Mask
3191 : AAStrokeMode::Geometry
;
3193 return AAStrokeMode::Unsupported
;
3195 return AAStrokeMode::Unsupported
;
3199 // Render an AA-Stroke'd vertex range into an R8 mask texture for subsequent
3201 already_AddRefed
<TextureHandle
> SharedContextWebgl::DrawStrokeMask(
3202 const PathVertexRange
& aVertexRange
, const IntSize
& aSize
) {
3203 // Allocate a new texture handle to store the rendered mask.
3204 RefPtr
<TextureHandle
> handle
=
3205 AllocateTextureHandle(SurfaceFormat::A8
, aSize
, true, true);
3210 IntRect texBounds
= handle
->GetBounds();
3211 BackingTexture
* backing
= handle
->GetBackingTexture();
3212 if (!backing
->IsInitialized()) {
3213 // If the backing texture is uninitialized, it needs its sampling parameters
3214 // set for later use.
3215 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, backing
->GetWebGLTexture());
3216 mWebgl
->TexStorage(LOCAL_GL_TEXTURE_2D
, 1, LOCAL_GL_R8
,
3217 {uint32_t(backing
->GetSize().width
),
3218 uint32_t(backing
->GetSize().height
), 1});
3219 InitTexParameters(backing
->GetWebGLTexture());
3223 // Set up a scratch framebuffer to render to the appropriate sub-texture of
3224 // the backing texture.
3225 if (!mScratchFramebuffer
) {
3226 mScratchFramebuffer
= mWebgl
->CreateFramebuffer();
3228 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mScratchFramebuffer
);
3229 webgl::FbAttachInfo attachInfo
;
3230 attachInfo
.tex
= backing
->GetWebGLTexture();
3231 mWebgl
->FramebufferAttach(LOCAL_GL_FRAMEBUFFER
, LOCAL_GL_COLOR_ATTACHMENT0
,
3232 LOCAL_GL_TEXTURE_2D
, attachInfo
);
3233 mWebgl
->Viewport(texBounds
.x
, texBounds
.y
, texBounds
.width
, texBounds
.height
);
3234 EnableScissor(texBounds
);
3235 if (!backing
->IsInitialized()) {
3236 backing
->MarkInitialized();
3237 // WebGL implicitly clears the backing texture the first time it is used.
3239 // Ensure the mask background is clear.
3240 mWebgl
->ClearColor(0.0f
, 0.0f
, 0.0f
, 0.0f
);
3241 mWebgl
->Clear(LOCAL_GL_COLOR_BUFFER_BIT
);
3244 // Reset any blending when drawing the mask.
3245 SetBlendState(CompositionOp::OP_OVER
);
3247 // Set up the solid color shader to draw a simple opaque mask.
3248 if (mLastProgram
!= mSolidProgram
) {
3249 mWebgl
->UseProgram(mSolidProgram
);
3250 mLastProgram
= mSolidProgram
;
3252 Array
<float, 2> viewportData
= {float(texBounds
.width
),
3253 float(texBounds
.height
)};
3254 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramViewport
, viewportData
,
3255 mSolidProgramUniformState
.mViewport
);
3256 Array
<float, 1> aaData
= {0.0f
};
3257 MaybeUniformData(LOCAL_GL_FLOAT
, mSolidProgramAA
, aaData
,
3258 mSolidProgramUniformState
.mAA
);
3259 Array
<float, 4> clipData
= {-0.5f
, -0.5f
, float(texBounds
.width
) + 0.5f
,
3260 float(texBounds
.height
) + 0.5f
};
3261 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramClipBounds
, clipData
,
3262 mSolidProgramUniformState
.mClipBounds
);
3263 Array
<float, 4> colorData
= {1.0f
, 1.0f
, 1.0f
, 1.0f
};
3264 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramColor
, colorData
,
3265 mSolidProgramUniformState
.mColor
);
3266 Array
<float, 6> xformData
= {1.0f
, 0.0f
, 0.0f
, 1.0f
, 0.0f
, 0.0f
};
3267 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramTransform
, xformData
,
3268 mSolidProgramUniformState
.mTransform
);
3270 // Ensure the current clip mask is ignored.
3271 RefPtr
<WebGLTexture
> prevClipMask
= mLastClipMask
;
3274 // Draw the mask using the supplied path vertex range.
3275 DrawTriangles(aVertexRange
);
3277 // Restore the previous framebuffer state.
3278 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mCurrentTarget
->mFramebuffer
);
3279 mWebgl
->Viewport(0, 0, mViewportSize
.width
, mViewportSize
.height
);
3281 SetClipMask(prevClipMask
);
3284 return handle
.forget();
3287 bool SharedContextWebgl::DrawPathAccel(
3288 const Path
* aPath
, const Pattern
& aPattern
, const DrawOptions
& aOptions
,
3289 const StrokeOptions
* aStrokeOptions
, bool aAllowStrokeAlpha
,
3290 const ShadowOptions
* aShadow
, bool aCacheable
, const Matrix
* aPathXform
) {
3291 // Get the transformed bounds for the path and conservatively check if the
3292 // bounds overlap the canvas.
3293 const PathSkia
* pathSkia
= static_cast<const PathSkia
*>(aPath
);
3294 const Matrix
& currentTransform
= mCurrentTarget
->GetTransform();
3295 Matrix pathXform
= currentTransform
;
3296 // If there is a path-specific transform that shouldn't be applied to the
3297 // pattern, then generate a matrix that should only be used with the Skia
3300 pathXform
.PreMultiply(*aPathXform
);
3302 Rect bounds
= pathSkia
->GetFastBounds(pathXform
, aStrokeOptions
);
3303 // If the path is empty, then there is nothing to draw.
3304 if (bounds
.IsEmpty()) {
3307 IntRect
viewport(IntPoint(), mViewportSize
);
3309 // Inflate the bounds to account for the blur radius.
3310 bounds
+= aShadow
->mOffset
;
3311 int32_t blurRadius
= aShadow
->BlurRadius();
3312 bounds
.Inflate(blurRadius
);
3313 viewport
.Inflate(blurRadius
);
3315 Point realOrigin
= bounds
.TopLeft();
3317 // Quantize the path origin to increase the reuse of cache entries.
3320 bounds
.Scale(0.25f
);
3322 Point quantizedOrigin
= bounds
.TopLeft();
3323 // If the path doesn't intersect the viewport, then there is nothing to draw.
3324 IntRect intBounds
= RoundedOut(bounds
).Intersect(viewport
);
3325 if (intBounds
.IsEmpty()) {
3328 // Nudge the bounds to account for the quantization rounding.
3329 Rect quantBounds
= Rect(intBounds
) + (realOrigin
- quantizedOrigin
);
3330 // If the pattern is a solid color, then this will be used along with a path
3331 // mask to render the path, as opposed to baking the pattern into the cached
3333 Maybe
<DeviceColor
> color
=
3334 aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
3335 ? Some(DeviceColor(1, 1, 1, 1))
3336 : (aPattern
.GetType() == PatternType::COLOR
3337 ? Some(static_cast<const ColorPattern
&>(aPattern
).mColor
)
3339 // Look for an existing path cache entry, if possible, or otherwise create
3340 // one. If the draw request is not cacheable, then don't create an entry.
3341 RefPtr
<PathCacheEntry
> entry
;
3342 RefPtr
<TextureHandle
> handle
;
3345 mPathCache
= MakeUnique
<PathCache
>();
3347 // Use a quantized, relative (to its bounds origin) version of the path as
3348 // a cache key to help limit cache bloat.
3349 Maybe
<QuantizedPath
> qp
= GenerateQuantizedPath(
3350 mWGRPathBuilder
, pathSkia
->GetPath(), quantBounds
, pathXform
);
3354 entry
= mPathCache
->FindOrInsertEntry(
3355 std::move(*qp
), color
? nullptr : &aPattern
, aStrokeOptions
,
3356 currentTransform
, intBounds
, quantizedOrigin
,
3357 aShadow
? aShadow
->mSigma
: -1.0f
);
3361 handle
= entry
->GetHandle();
3364 // If there is a shadow, it needs to draw with the shadow color rather than
3366 Maybe
<DeviceColor
> shadowColor
= color
;
3367 if (aShadow
&& aOptions
.mCompositionOp
!= CompositionOp::OP_CLEAR
) {
3368 shadowColor
= Some(aShadow
->mColor
);
3370 shadowColor
->a
*= color
->a
;
3373 SamplingFilter filter
=
3374 aShadow
? SamplingFilter::GOOD
: GetSamplingFilter(aPattern
);
3375 if (handle
&& handle
->IsValid()) {
3376 // If the entry has a valid texture handle still, use it. However, the
3377 // entry texture is assumed to be located relative to its previous bounds.
3378 // We need to offset the pattern by the difference between its new unclipped
3379 // origin and its previous previous unclipped origin. Then when we finally
3380 // draw a rectangle at the expected new bounds, it will overlap the portion
3381 // of the old entry texture we actually need to sample from.
3383 (realOrigin
- entry
->GetOrigin()) + entry
->GetBounds().TopLeft();
3384 SurfacePattern
pathPattern(nullptr, ExtendMode::CLAMP
,
3385 Matrix::Translation(offset
), filter
);
3386 return DrawRectAccel(quantBounds
, pathPattern
, aOptions
, shadowColor
,
3387 &handle
, false, true, true);
3390 if (mPathVertexCapacity
> 0 && !handle
&& entry
&& !aShadow
&&
3391 aOptions
.mAntialiasMode
!= AntialiasMode::NONE
&&
3392 SupportsPattern(aPattern
) &&
3393 entry
->GetPath().mPath
.num_types
<= mPathMaxComplexity
) {
3394 if (entry
->GetVertexRange().IsValid()) {
3395 // If there is a valid cached vertex data in the path vertex buffer, then
3396 // just draw that. We must draw at integer pixel boundaries (using
3397 // intBounds instead of quantBounds) due to WGR's reliance on pixel center
3399 mCurrentTarget
->mProfile
.OnCacheHit();
3400 return DrawRectAccel(Rect(intBounds
.TopLeft(), Size(1, 1)), aPattern
,
3401 aOptions
, Nothing(), nullptr, false, true, true,
3402 false, nullptr, &entry
->GetVertexRange());
3405 // printf_stderr("Generating... verbs %d, points %d\n",
3406 // int(pathSkia->GetPath().countVerbs()),
3407 // int(pathSkia->GetPath().countPoints()));
3408 WGR::OutputVertex
* outputBuffer
= nullptr;
3409 size_t outputBufferCapacity
= 0;
3410 if (mWGROutputBuffer
) {
3411 outputBuffer
= mWGROutputBuffer
.get();
3412 outputBufferCapacity
= mPathVertexCapacity
/ sizeof(WGR::OutputVertex
);
3414 Maybe
<WGR::VertexBuffer
> wgrVB
;
3415 Maybe
<AAStroke::VertexBuffer
> strokeVB
;
3416 if (!aStrokeOptions
) {
3417 if (aPath
== mUnitCirclePath
) {
3418 auto scaleFactors
= pathXform
.ScaleFactors();
3419 if (scaleFactors
.AreScalesSame()) {
3420 Point center
= pathXform
.GetTranslation() - quantBounds
.TopLeft();
3421 float radius
= scaleFactors
.xScale
;
3422 AAStroke::VertexBuffer vb
= AAStroke::aa_stroke_filled_circle(
3423 center
.x
, center
.y
, radius
, (AAStroke::OutputVertex
*)outputBuffer
,
3424 outputBufferCapacity
);
3425 if (!vb
.len
|| (outputBuffer
&& vb
.len
> outputBufferCapacity
)) {
3426 AAStroke::aa_stroke_vertex_buffer_release(vb
);
3428 strokeVB
= Some(vb
);
3433 wgrVB
= GeneratePathVertexBuffer(
3434 entry
->GetPath(), IntRect(-intBounds
.TopLeft(), mViewportSize
),
3435 mRasterizationTruncates
, outputBuffer
, outputBufferCapacity
);
3438 if (mPathAAStroke
&&
3439 SupportsAAStroke(aPattern
, aOptions
, *aStrokeOptions
,
3440 aAllowStrokeAlpha
) != AAStrokeMode::Unsupported
) {
3441 auto scaleFactors
= currentTransform
.ScaleFactors();
3442 if (scaleFactors
.AreScalesSame()) {
3443 strokeVB
= GenerateStrokeVertexBuffer(
3444 entry
->GetPath(), aStrokeOptions
, scaleFactors
.xScale
,
3445 outputBuffer
, outputBufferCapacity
);
3448 if (!strokeVB
&& mPathWGRStroke
) {
3449 // If stroking, then generate a path to fill the stroked region. This
3450 // path will need to be quantized again because it differs from the
3451 // path used for the cache entry, but this allows us to avoid
3452 // generating a fill path on a cache hit.
3453 Maybe
<Rect
> cullRect
;
3454 Matrix invTransform
= currentTransform
;
3455 if (invTransform
.Invert()) {
3456 // Transform the stroking clip rect from device space to local
3458 Rect invRect
= invTransform
.TransformBounds(Rect(mClipRect
));
3460 cullRect
= Some(invRect
);
3463 if (pathSkia
->GetFillPath(*aStrokeOptions
, pathXform
, fillPath
,
3465 // printf_stderr(" stroke fill... verbs %d, points %d\n",
3466 // int(fillPath.countVerbs()),
3467 // int(fillPath.countPoints()));
3468 if (Maybe
<QuantizedPath
> qp
= GenerateQuantizedPath(
3469 mWGRPathBuilder
, fillPath
, quantBounds
, pathXform
)) {
3470 wgrVB
= GeneratePathVertexBuffer(
3471 *qp
, IntRect(-intBounds
.TopLeft(), mViewportSize
),
3472 mRasterizationTruncates
, outputBuffer
, outputBufferCapacity
);
3477 if (wgrVB
|| strokeVB
) {
3478 const uint8_t* vbData
=
3479 wgrVB
? (const uint8_t*)wgrVB
->data
: (const uint8_t*)strokeVB
->data
;
3480 if (outputBuffer
&& !vbData
) {
3481 vbData
= (const uint8_t*)outputBuffer
;
3483 size_t vbLen
= wgrVB
? wgrVB
->len
: strokeVB
->len
;
3484 uint32_t vertexBytes
= uint32_t(
3485 std::min(vbLen
* sizeof(WGR::OutputVertex
), size_t(UINT32_MAX
)));
3486 // printf_stderr(" ... %d verts, %d bytes\n", int(vbLen),
3487 // int(vertexBytes));
3488 if (vertexBytes
> mPathVertexCapacity
- mPathVertexOffset
&&
3489 vertexBytes
<= mPathVertexCapacity
- sizeof(kRectVertexData
)) {
3490 // If the vertex data is too large to fit in the remaining path vertex
3491 // buffer, then orphan the contents of the vertex buffer to make room
3494 mPathCache
->ClearVertexRanges();
3496 ResetPathVertexBuffer(false);
3498 if (vertexBytes
<= mPathVertexCapacity
- mPathVertexOffset
) {
3499 // If there is actually room to fit the vertex data in the vertex buffer
3500 // after orphaning as necessary, then upload the data to the next
3501 // available offset in the buffer.
3502 PathVertexRange
vertexRange(
3503 uint32_t(mPathVertexOffset
/ sizeof(WGR::OutputVertex
)),
3505 // printf_stderr(" ... offset %d\n", mPathVertexOffset);
3506 // Normal glBufferSubData interleaved with draw calls causes performance
3507 // issues on Mali, so use our special unsynchronized version. This is
3508 // safe as we never update regions referenced by pending draw calls.
3509 mWebgl
->BufferSubData(LOCAL_GL_ARRAY_BUFFER
, mPathVertexOffset
,
3510 vertexBytes
, vbData
,
3511 /* unsynchronized */ true);
3512 mPathVertexOffset
+= vertexBytes
;
3514 WGR::wgr_vertex_buffer_release(wgrVB
.ref());
3516 AAStroke::aa_stroke_vertex_buffer_release(strokeVB
.ref());
3518 if (strokeVB
&& aStrokeOptions
&&
3519 SupportsAAStroke(aPattern
, aOptions
, *aStrokeOptions
,
3520 aAllowStrokeAlpha
) == AAStrokeMode::Mask
) {
3521 // Attempt to generate a stroke mask for path.
3522 if (RefPtr
<TextureHandle
> handle
=
3523 DrawStrokeMask(vertexRange
, intBounds
.Size())) {
3524 // Finally, draw the rendered stroke mask.
3526 entry
->Link(handle
);
3528 mCurrentTarget
->mProfile
.OnCacheMiss();
3529 SurfacePattern
maskPattern(
3530 nullptr, ExtendMode::CLAMP
,
3531 Matrix::Translation(quantBounds
.TopLeft()),
3532 SamplingFilter::GOOD
);
3533 return DrawRectAccel(quantBounds
, maskPattern
, aOptions
, color
,
3534 &handle
, false, true, true);
3537 // Remember the vertex range in the cache entry so that it can be
3540 entry
->SetVertexRange(vertexRange
);
3543 // Finally, draw the uploaded vertex data.
3544 mCurrentTarget
->mProfile
.OnCacheMiss();
3545 return DrawRectAccel(Rect(intBounds
.TopLeft(), Size(1, 1)), aPattern
,
3546 aOptions
, Nothing(), nullptr, false, true, true,
3547 false, nullptr, &vertexRange
);
3551 WGR::wgr_vertex_buffer_release(wgrVB
.ref());
3553 AAStroke::aa_stroke_vertex_buffer_release(strokeVB
.ref());
3556 // If we failed to draw the vertex data for some reason, then fall through
3557 // to the texture rasterization path.
3561 // If a stroke path covers too much screen area, it is likely that most is
3562 // empty space in the interior. This usually imposes too high a cost versus
3563 // just rasterizing without acceleration. Note that AA-Stroke generally
3564 // produces more acceptable amounts of geometry for larger paths, so we do
3565 // this heuristic after we attempt AA-Stroke.
3566 if (aStrokeOptions
&&
3567 intBounds
.width
* intBounds
.height
>
3568 (mViewportSize
.width
/ 2) * (mViewportSize
.height
/ 2)) {
3572 // If there isn't a valid texture handle, then we need to rasterize the
3573 // path in a software canvas and upload this to a texture. Solid color
3574 // patterns will be rendered as a path mask that can then be modulated
3575 // with any color. Other pattern types have to rasterize the pattern
3576 // directly into the cached texture.
3578 RefPtr
<DrawTargetSkia
> pathDT
= new DrawTargetSkia
;
3579 if (pathDT
->Init(intBounds
.Size(), color
|| aShadow
3581 : SurfaceFormat::B8G8R8A8
)) {
3582 Point offset
= -quantBounds
.TopLeft();
3584 // Ensure the the shadow is drawn at the requested offset
3585 offset
+= aShadow
->mOffset
;
3587 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
,
3588 aOptions
.mAntialiasMode
);
3589 static const ColorPattern
maskPattern(DeviceColor(1.0f
, 1.0f
, 1.0f
, 1.0f
));
3590 const Pattern
& cachePattern
= color
? maskPattern
: aPattern
;
3591 // If the source pattern is a DrawTargetWebgl snapshot, we may shift
3592 // targets when drawing the path, so back up the old target.
3593 DrawTargetWebgl
* oldTarget
= mCurrentTarget
;
3595 RefPtr
<const Path
> path
;
3596 if (color
|| !aPathXform
) {
3597 // If the pattern is transform invariant or there is no pathXform, then
3598 // it is safe to use the path directly.
3600 pathDT
->SetTransform(pathXform
* Matrix::Translation(offset
));
3602 // If there is a pathXform, then pre-apply that to the path to avoid
3603 // altering the pattern.
3604 RefPtr
<PathBuilder
> builder
=
3605 aPath
->TransformedCopyToBuilder(*aPathXform
);
3606 path
= builder
->Finish();
3607 pathDT
->SetTransform(currentTransform
* Matrix::Translation(offset
));
3609 if (aStrokeOptions
) {
3610 pathDT
->Stroke(path
, cachePattern
, *aStrokeOptions
, drawOptions
);
3612 pathDT
->Fill(path
, cachePattern
, drawOptions
);
3615 if (aShadow
&& aShadow
->mSigma
> 0.0f
) {
3616 // Blur the shadow if required.
3617 uint8_t* data
= nullptr;
3620 SurfaceFormat format
= SurfaceFormat::UNKNOWN
;
3621 if (pathDT
->LockBits(&data
, &size
, &stride
, &format
)) {
3622 AlphaBoxBlur
blur(Rect(pathDT
->GetRect()), stride
, aShadow
->mSigma
,
3625 pathDT
->ReleaseBits(data
);
3628 RefPtr
<SourceSurface
> pathSurface
= pathDT
->Snapshot();
3630 // If the target changed, try to restore it.
3631 if (mCurrentTarget
!= oldTarget
&& !oldTarget
->PrepareContext()) {
3634 SurfacePattern
pathPattern(pathSurface
, ExtendMode::CLAMP
,
3635 Matrix::Translation(quantBounds
.TopLeft()),
3637 // Try and upload the rasterized path to a texture. If there is a
3638 // valid texture handle after this, then link it to the entry.
3639 // Otherwise, we might have to fall back to software drawing the
3640 // path, so unlink it from the entry.
3641 if (DrawRectAccel(quantBounds
, pathPattern
, aOptions
, shadowColor
,
3642 &handle
, false, true) &&
3645 entry
->Link(handle
);
3657 void DrawTargetWebgl::DrawPath(const Path
* aPath
, const Pattern
& aPattern
,
3658 const DrawOptions
& aOptions
,
3659 const StrokeOptions
* aStrokeOptions
,
3660 bool aAllowStrokeAlpha
) {
3661 // If there is a WebGL context, then try to cache the path to avoid slow
3663 if (ShouldAccelPath(aOptions
, aStrokeOptions
) &&
3664 mSharedContext
->DrawPathAccel(aPath
, aPattern
, aOptions
, aStrokeOptions
,
3665 aAllowStrokeAlpha
)) {
3669 // There was no path cache entry available to use, so fall back to drawing the
3671 MarkSkiaChanged(aOptions
);
3672 if (aStrokeOptions
) {
3673 mSkia
->Stroke(aPath
, aPattern
, *aStrokeOptions
, aOptions
);
3675 mSkia
->Fill(aPath
, aPattern
, aOptions
);
3679 // DrawCircleAccel is a more specialized version of DrawPathAccel that attempts
3680 // to cache a unit circle.
3681 bool SharedContextWebgl::DrawCircleAccel(const Point
& aCenter
, float aRadius
,
3682 const Pattern
& aPattern
,
3683 const DrawOptions
& aOptions
,
3684 const StrokeOptions
* aStrokeOptions
) {
3685 // Cache a unit circle and transform it to avoid creating a path repeatedly.
3686 if (!mUnitCirclePath
) {
3687 mUnitCirclePath
= MakePathForCircle(*mCurrentTarget
, Point(0, 0), 1);
3689 // Scale and translate the circle to the desired shape.
3690 Matrix
circleXform(aRadius
, 0, 0, aRadius
, aCenter
.x
, aCenter
.y
);
3691 return DrawPathAccel(mUnitCirclePath
, aPattern
, aOptions
, aStrokeOptions
,
3692 true, nullptr, true, &circleXform
);
3695 void DrawTargetWebgl::DrawCircle(const Point
& aOrigin
, float aRadius
,
3696 const Pattern
& aPattern
,
3697 const DrawOptions
& aOptions
,
3698 const StrokeOptions
* aStrokeOptions
) {
3699 if (ShouldAccelPath(aOptions
, aStrokeOptions
) &&
3700 mSharedContext
->DrawCircleAccel(aOrigin
, aRadius
, aPattern
, aOptions
,
3705 MarkSkiaChanged(aOptions
);
3706 if (aStrokeOptions
) {
3707 mSkia
->StrokeCircle(aOrigin
, aRadius
, aPattern
, *aStrokeOptions
, aOptions
);
3709 mSkia
->FillCircle(aOrigin
, aRadius
, aPattern
, aOptions
);
3713 void DrawTargetWebgl::DrawSurface(SourceSurface
* aSurface
, const Rect
& aDest
,
3714 const Rect
& aSource
,
3715 const DrawSurfaceOptions
& aSurfOptions
,
3716 const DrawOptions
& aOptions
) {
3717 Matrix matrix
= Matrix::Scaling(aDest
.width
/ aSource
.width
,
3718 aDest
.height
/ aSource
.height
);
3719 matrix
.PreTranslate(-aSource
.x
, -aSource
.y
);
3720 matrix
.PostTranslate(aDest
.x
, aDest
.y
);
3721 SurfacePattern
pattern(aSurface
, ExtendMode::CLAMP
, matrix
,
3722 aSurfOptions
.mSamplingFilter
);
3723 DrawRect(aDest
, pattern
, aOptions
);
3726 void DrawTargetWebgl::Mask(const Pattern
& aSource
, const Pattern
& aMask
,
3727 const DrawOptions
& aOptions
) {
3728 if (!SupportsDrawOptions(aOptions
) ||
3729 aMask
.GetType() != PatternType::SURFACE
||
3730 aSource
.GetType() != PatternType::COLOR
) {
3731 MarkSkiaChanged(aOptions
);
3732 mSkia
->Mask(aSource
, aMask
, aOptions
);
3735 auto sourceColor
= static_cast<const ColorPattern
&>(aSource
).mColor
;
3736 auto maskPattern
= static_cast<const SurfacePattern
&>(aMask
);
3737 DrawRect(Rect(IntRect(IntPoint(), maskPattern
.mSurface
->GetSize())),
3738 maskPattern
, aOptions
, Some(sourceColor
));
3741 void DrawTargetWebgl::MaskSurface(const Pattern
& aSource
, SourceSurface
* aMask
,
3742 Point aOffset
, const DrawOptions
& aOptions
) {
3743 if (!SupportsDrawOptions(aOptions
) ||
3744 aSource
.GetType() != PatternType::COLOR
) {
3745 MarkSkiaChanged(aOptions
);
3746 mSkia
->MaskSurface(aSource
, aMask
, aOffset
, aOptions
);
3748 auto sourceColor
= static_cast<const ColorPattern
&>(aSource
).mColor
;
3749 SurfacePattern
pattern(aMask
, ExtendMode::CLAMP
,
3750 Matrix::Translation(aOffset
));
3751 DrawRect(Rect(aOffset
, Size(aMask
->GetSize())), pattern
, aOptions
,
3756 // Extract the surface's alpha values into an A8 surface.
3757 static already_AddRefed
<DataSourceSurface
> ExtractAlpha(SourceSurface
* aSurface
,
3758 bool aAllowSubpixelAA
) {
3759 RefPtr
<DataSourceSurface
> surfaceData
= aSurface
->GetDataSurface();
3763 DataSourceSurface::ScopedMap
srcMap(surfaceData
, DataSourceSurface::READ
);
3764 if (!srcMap
.IsMapped()) {
3767 IntSize size
= surfaceData
->GetSize();
3768 RefPtr
<DataSourceSurface
> alpha
=
3769 Factory::CreateDataSourceSurface(size
, SurfaceFormat::A8
, false);
3773 DataSourceSurface::ScopedMap
dstMap(alpha
, DataSourceSurface::WRITE
);
3774 if (!dstMap
.IsMapped()) {
3777 // For subpixel masks, ignore the alpha and instead sample one of the color
3778 // channels as if they were alpha.
3780 srcMap
.GetData(), srcMap
.GetStride(),
3781 aAllowSubpixelAA
? SurfaceFormat::A8R8G8B8
: surfaceData
->GetFormat(),
3782 dstMap
.GetData(), dstMap
.GetStride(), SurfaceFormat::A8
, size
);
3783 return alpha
.forget();
3786 void DrawTargetWebgl::DrawShadow(const Path
* aPath
, const Pattern
& aPattern
,
3787 const ShadowOptions
& aShadow
,
3788 const DrawOptions
& aOptions
,
3789 const StrokeOptions
* aStrokeOptions
) {
3790 if (!aPath
|| aPath
->GetBackendType() != BackendType::SKIA
) {
3794 // If there is a WebGL context, then try to cache the path to avoid slow
3796 if (ShouldAccelPath(aOptions
, aStrokeOptions
) &&
3797 mSharedContext
->DrawPathAccel(aPath
, aPattern
, aOptions
, aStrokeOptions
,
3802 // There was no path cache entry available to use, so fall back to drawing the
3804 MarkSkiaChanged(aOptions
);
3805 mSkia
->DrawShadow(aPath
, aPattern
, aShadow
, aOptions
, aStrokeOptions
);
3808 void DrawTargetWebgl::DrawSurfaceWithShadow(SourceSurface
* aSurface
,
3810 const ShadowOptions
& aShadow
,
3811 CompositionOp aOperator
) {
3812 DrawOptions
options(1.0f
, aOperator
);
3813 if (ShouldAccelPath(options
, nullptr)) {
3814 SurfacePattern
pattern(aSurface
, ExtendMode::CLAMP
,
3815 Matrix::Translation(aDest
));
3817 skiaPath
.addRect(RectToSkRect(Rect(aSurface
->GetRect()) + aDest
));
3818 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
3819 AutoRestoreTransform
restore(this);
3820 SetTransform(Matrix());
3821 if (mSharedContext
->DrawPathAccel(path
, pattern
, options
, nullptr, false,
3823 DrawRect(Rect(aSurface
->GetRect()) + aDest
, pattern
, options
);
3828 MarkSkiaChanged(options
);
3829 mSkia
->DrawSurfaceWithShadow(aSurface
, aDest
, aShadow
, aOperator
);
3832 already_AddRefed
<PathBuilder
> DrawTargetWebgl::CreatePathBuilder(
3833 FillRule aFillRule
) const {
3834 return mSkia
->CreatePathBuilder(aFillRule
);
3837 void DrawTargetWebgl::SetTransform(const Matrix
& aTransform
) {
3838 DrawTarget::SetTransform(aTransform
);
3839 mSkia
->SetTransform(aTransform
);
3842 void DrawTargetWebgl::StrokeRect(const Rect
& aRect
, const Pattern
& aPattern
,
3843 const StrokeOptions
& aStrokeOptions
,
3844 const DrawOptions
& aOptions
) {
3846 MarkSkiaChanged(aOptions
);
3847 mSkia
->StrokeRect(aRect
, aPattern
, aStrokeOptions
, aOptions
);
3849 // If the stroke options are unsupported, then transform the rect to a path
3850 // so it can be cached.
3852 skiaPath
.addRect(RectToSkRect(aRect
));
3853 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
3854 DrawPath(path
, aPattern
, aOptions
, &aStrokeOptions
, true);
3858 static inline bool IsThinLine(const Matrix
& aTransform
,
3859 const StrokeOptions
& aStrokeOptions
) {
3860 auto scale
= aTransform
.ScaleFactors();
3861 return std::max(scale
.xScale
, scale
.yScale
) * aStrokeOptions
.mLineWidth
<= 1;
3864 bool DrawTargetWebgl::StrokeLineAccel(const Point
& aStart
, const Point
& aEnd
,
3865 const Pattern
& aPattern
,
3866 const StrokeOptions
& aStrokeOptions
,
3867 const DrawOptions
& aOptions
,
3869 // Approximating a wide line as a rectangle works only with certain cap styles
3870 // in the general case (butt or square). However, if the line width is
3871 // sufficiently thin, we can either ignore the round cap (or treat it like
3872 // square for zero-length lines) without causing objectionable artifacts.
3873 // Lines may sometimes be used in closed paths that immediately reverse back,
3874 // in which case we need to use mLineJoin instead of mLineCap to determine the
3877 aClosed
? (aStrokeOptions
.mLineJoin
== JoinStyle::ROUND
? CapStyle::ROUND
3879 : aStrokeOptions
.mLineCap
;
3880 if (mWebglValid
&& SupportsPattern(aPattern
) &&
3881 (capStyle
!= CapStyle::ROUND
||
3882 IsThinLine(GetTransform(), aStrokeOptions
)) &&
3883 aStrokeOptions
.mDashPattern
== nullptr && aStrokeOptions
.mLineWidth
> 0) {
3884 // Treat the line as a rectangle whose center-line is the supplied line and
3885 // for which the height is the supplied line width. Generate a matrix that
3886 // maps the X axis to the orientation of the line and the Y axis to the
3887 // normal vector to the line. This only works if the line caps are squared,
3888 // as rounded rectangles are currently not supported for round line caps.
3889 Point start
= aStart
;
3890 Point dirX
= aEnd
- aStart
;
3892 float dirLen
= dirX
.Length();
3893 float scale
= aStrokeOptions
.mLineWidth
;
3894 if (dirLen
== 0.0f
) {
3895 // If the line is zero-length, then only a cap is rendered.
3897 case CapStyle::BUTT
:
3898 // The cap doesn't extend beyond the line so nothing is drawn.
3900 case CapStyle::ROUND
:
3901 case CapStyle::SQUARE
:
3902 // Draw a unit square centered at the single point.
3903 dirX
= Point(scale
, 0.0f
);
3904 dirY
= Point(0.0f
, scale
);
3905 // Offset the start by half a unit.
3906 start
.x
-= 0.5f
* scale
;
3910 // Make the scale map to a single unit length.
3912 dirY
= Point(-dirX
.y
, dirX
.x
) * scale
;
3913 if (capStyle
== CapStyle::SQUARE
) {
3914 // Offset the start by half a unit.
3915 start
-= (dirX
* scale
) * 0.5f
;
3916 // Ensure the extent also accounts for the start and end cap.
3917 dirX
+= dirX
* scale
;
3920 Matrix
lineXform(dirX
.x
, dirX
.y
, dirY
.x
, dirY
.y
, start
.x
- 0.5f
* dirY
.x
,
3921 start
.y
- 0.5f
* dirY
.y
);
3922 if (PrepareContext() &&
3923 mSharedContext
->DrawRectAccel(Rect(0, 0, 1, 1), aPattern
, aOptions
,
3924 Nothing(), nullptr, true, true, true,
3925 false, nullptr, nullptr, &lineXform
)) {
3932 void DrawTargetWebgl::StrokeLine(const Point
& aStart
, const Point
& aEnd
,
3933 const Pattern
& aPattern
,
3934 const StrokeOptions
& aStrokeOptions
,
3935 const DrawOptions
& aOptions
) {
3937 MarkSkiaChanged(aOptions
);
3938 mSkia
->StrokeLine(aStart
, aEnd
, aPattern
, aStrokeOptions
, aOptions
);
3939 } else if (!StrokeLineAccel(aStart
, aEnd
, aPattern
, aStrokeOptions
,
3941 // If the stroke options are unsupported, then transform the line to a path
3942 // so it can be cached.
3944 skiaPath
.moveTo(PointToSkPoint(aStart
));
3945 skiaPath
.lineTo(PointToSkPoint(aEnd
));
3946 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
3947 DrawPath(path
, aPattern
, aOptions
, &aStrokeOptions
, true);
3951 void DrawTargetWebgl::Stroke(const Path
* aPath
, const Pattern
& aPattern
,
3952 const StrokeOptions
& aStrokeOptions
,
3953 const DrawOptions
& aOptions
) {
3954 if (!aPath
|| aPath
->GetBackendType() != BackendType::SKIA
) {
3957 const auto& skiaPath
= static_cast<const PathSkia
*>(aPath
)->GetPath();
3959 MarkSkiaChanged(aOptions
);
3960 mSkia
->Stroke(aPath
, aPattern
, aStrokeOptions
, aOptions
);
3964 // Avoid using Skia's isLine here because some paths erroneously include a
3965 // closePath at the end, causing isLine to not detect the line. In that case
3966 // we just draw a line in reverse right over the original line.
3967 int numVerbs
= skiaPath
.countVerbs();
3968 bool allowStrokeAlpha
= false;
3969 if (numVerbs
>= 2 && numVerbs
<= 3) {
3971 skiaPath
.getVerbs(verbs
, numVerbs
);
3972 if (verbs
[0] == SkPath::kMove_Verb
&& verbs
[1] == SkPath::kLine_Verb
&&
3973 (numVerbs
< 3 || verbs
[2] == SkPath::kClose_Verb
)) {
3974 bool closed
= numVerbs
>= 3;
3975 Point start
= SkPointToPoint(skiaPath
.getPoint(0));
3976 Point end
= SkPointToPoint(skiaPath
.getPoint(1));
3977 if (StrokeLineAccel(start
, end
, aPattern
, aStrokeOptions
, aOptions
,
3980 StrokeLineAccel(end
, start
, aPattern
, aStrokeOptions
, aOptions
, true);
3984 // If accelerated line drawing failed, just treat it as a path.
3985 allowStrokeAlpha
= true;
3989 DrawPath(aPath
, aPattern
, aOptions
, &aStrokeOptions
, allowStrokeAlpha
);
3992 void DrawTargetWebgl::StrokeCircle(const Point
& aOrigin
, float aRadius
,
3993 const Pattern
& aPattern
,
3994 const StrokeOptions
& aStrokeOptions
,
3995 const DrawOptions
& aOptions
) {
3996 DrawCircle(aOrigin
, aRadius
, aPattern
, aOptions
, &aStrokeOptions
);
3999 bool DrawTargetWebgl::ShouldUseSubpixelAA(ScaledFont
* aFont
,
4000 const DrawOptions
& aOptions
) {
4001 AntialiasMode aaMode
= aFont
->GetDefaultAAMode();
4002 if (aOptions
.mAntialiasMode
!= AntialiasMode::DEFAULT
) {
4003 aaMode
= aOptions
.mAntialiasMode
;
4005 return GetPermitSubpixelAA() &&
4006 (aaMode
== AntialiasMode::DEFAULT
||
4007 aaMode
== AntialiasMode::SUBPIXEL
) &&
4008 aOptions
.mCompositionOp
== CompositionOp::OP_OVER
;
4011 void DrawTargetWebgl::StrokeGlyphs(ScaledFont
* aFont
,
4012 const GlyphBuffer
& aBuffer
,
4013 const Pattern
& aPattern
,
4014 const StrokeOptions
& aStrokeOptions
,
4015 const DrawOptions
& aOptions
) {
4016 if (!aFont
|| !aBuffer
.mNumGlyphs
) {
4020 bool useSubpixelAA
= ShouldUseSubpixelAA(aFont
, aOptions
);
4022 if (mWebglValid
&& SupportsDrawOptions(aOptions
) &&
4023 aPattern
.GetType() == PatternType::COLOR
&& PrepareContext() &&
4024 mSharedContext
->DrawGlyphsAccel(aFont
, aBuffer
, aPattern
, aOptions
,
4025 &aStrokeOptions
, useSubpixelAA
)) {
4029 if (useSubpixelAA
) {
4030 // Subpixel AA does not support layering because the subpixel masks can't
4031 // blend with the over op.
4034 MarkSkiaChanged(aOptions
);
4036 mSkia
->StrokeGlyphs(aFont
, aBuffer
, aPattern
, aStrokeOptions
, aOptions
);
4039 // Depending on whether we enable subpixel position for a given font, Skia may
4040 // round transformed coordinates differently on each axis. By default, text is
4041 // subpixel quantized horizontally and snapped to a whole integer vertical
4042 // baseline. Axis-flip transforms instead snap to horizontal boundaries while
4043 // subpixel quantizing along the vertical. For other types of transforms, Skia
4044 // just applies subpixel quantization to both axes.
4045 // We must duplicate the amount of quantization Skia applies carefully as a
4046 // boundary value such as 0.49 may round to 0.5 with subpixel quantization,
4047 // but if Skia actually snapped it to a whole integer instead, it would round
4048 // down to 0. If a subsequent glyph with offset 0.51 came in, we might
4049 // mistakenly round it down to 0.5, whereas Skia would round it up to 1. Thus
4050 // we would alias 0.49 and 0.51 to the same cache entry, while Skia would
4051 // actually snap the offset to 0 or 1, depending, resulting in mismatched
4053 static inline IntPoint
QuantizeScale(ScaledFont
* aFont
,
4054 const Matrix
& aTransform
) {
4055 if (!aFont
->UseSubpixelPosition()) {
4058 if (aTransform
._12
== 0) {
4059 // Glyphs are rendered subpixel horizontally, so snap vertically.
4062 if (aTransform
._11
== 0) {
4063 // Glyphs are rendered subpixel vertically, so snap horizontally.
4066 // The transform isn't aligned, so don't snap.
4070 // Skia only supports subpixel positioning to the nearest 1/4 fraction. It
4071 // would be wasteful to attempt to cache text runs with positioning that is
4072 // anymore precise than this. To prevent this cache bloat, we quantize the
4073 // transformed glyph positions to the nearest 1/4. The scaling factor for
4074 // the quantization is baked into the transform, so that if subpixel rounding
4075 // is used on a given axis, then the axis will be multiplied by 4 before
4076 // rounding. Since the quantized position is not used for rasterization, the
4077 // transform is safe to modify as such.
4078 static inline IntPoint
QuantizePosition(const Matrix
& aTransform
,
4079 const IntPoint
& aOffset
,
4080 const Point
& aPosition
) {
4081 return RoundedToInt(aTransform
.TransformPoint(aPosition
)) - aOffset
;
4084 // Get a quantized starting offset for the glyph buffer. We want this offset
4085 // to encapsulate the transform and buffer offset while still preserving the
4086 // relative subpixel positions of the glyphs this offset is subtracted from.
4087 static inline IntPoint
QuantizeOffset(const Matrix
& aTransform
,
4088 const IntPoint
& aQuantizeScale
,
4089 const GlyphBuffer
& aBuffer
) {
4091 RoundedToInt(aTransform
.TransformPoint(aBuffer
.mGlyphs
[0].mPosition
));
4092 offset
.x
.value
&= ~(aQuantizeScale
.x
.value
- 1);
4093 offset
.y
.value
&= ~(aQuantizeScale
.y
.value
- 1);
4097 // Hashes a glyph buffer to a single hash value that can be used for quick
4098 // comparisons. Each glyph position is transformed and quantized before
4100 HashNumber
GlyphCacheEntry::HashGlyphs(const GlyphBuffer
& aBuffer
,
4101 const Matrix
& aTransform
,
4102 const IntPoint
& aQuantizeScale
) {
4103 HashNumber hash
= 0;
4104 IntPoint offset
= QuantizeOffset(aTransform
, aQuantizeScale
, aBuffer
);
4105 for (size_t i
= 0; i
< aBuffer
.mNumGlyphs
; i
++) {
4106 const Glyph
& glyph
= aBuffer
.mGlyphs
[i
];
4107 hash
= AddToHash(hash
, glyph
.mIndex
);
4108 IntPoint pos
= QuantizePosition(aTransform
, offset
, glyph
.mPosition
);
4109 hash
= AddToHash(hash
, pos
.x
);
4110 hash
= AddToHash(hash
, pos
.y
);
4115 // Determines if an existing glyph cache entry matches an incoming text run.
4116 inline bool GlyphCacheEntry::MatchesGlyphs(
4117 const GlyphBuffer
& aBuffer
, const DeviceColor
& aColor
,
4118 const Matrix
& aTransform
, const IntPoint
& aQuantizeOffset
,
4119 const IntPoint
& aBoundsOffset
, const IntRect
& aClipRect
, HashNumber aHash
,
4120 const StrokeOptions
* aStrokeOptions
) {
4121 // First check if the hash matches to quickly reject the text run before any
4122 // more expensive checking. If it matches, then check if the color and
4123 // transform are the same.
4124 if (aHash
!= mHash
|| aBuffer
.mNumGlyphs
!= mBuffer
.mNumGlyphs
||
4125 aColor
!= mColor
|| !HasMatchingScale(aTransform
, mTransform
)) {
4128 // Finally check if all glyphs and their quantized positions match.
4129 for (size_t i
= 0; i
< aBuffer
.mNumGlyphs
; i
++) {
4130 const Glyph
& dst
= mBuffer
.mGlyphs
[i
];
4131 const Glyph
& src
= aBuffer
.mGlyphs
[i
];
4132 if (dst
.mIndex
!= src
.mIndex
||
4133 dst
.mPosition
!= Point(QuantizePosition(aTransform
, aQuantizeOffset
,
4138 // Check that stroke options actually match.
4139 if (aStrokeOptions
) {
4140 // If stroking, verify that the entry is also stroked with the same options.
4141 if (!(mStrokeOptions
&& *aStrokeOptions
== *mStrokeOptions
)) {
4144 } else if (mStrokeOptions
) {
4145 // If not stroking, check if the entry is stroked. If so, don't match.
4148 // Verify that the full bounds, once translated and clipped, are equal to the
4150 return (mFullBounds
+ aBoundsOffset
)
4151 .Intersect(aClipRect
)
4152 .IsEqualEdges(GetBounds() + aBoundsOffset
);
4155 GlyphCacheEntry::GlyphCacheEntry(const GlyphBuffer
& aBuffer
,
4156 const DeviceColor
& aColor
,
4157 const Matrix
& aTransform
,
4158 const IntPoint
& aQuantizeScale
,
4159 const IntRect
& aBounds
,
4160 const IntRect
& aFullBounds
, HashNumber aHash
,
4161 StoredStrokeOptions
* aStrokeOptions
)
4162 : CacheEntryImpl
<GlyphCacheEntry
>(aTransform
, aBounds
, aHash
),
4164 mFullBounds(aFullBounds
),
4165 mStrokeOptions(aStrokeOptions
) {
4166 // Store a copy of the glyph buffer with positions already quantized for fast
4167 // comparison later.
4168 Glyph
* glyphs
= new Glyph
[aBuffer
.mNumGlyphs
];
4169 IntPoint offset
= QuantizeOffset(aTransform
, aQuantizeScale
, aBuffer
);
4170 // Make the bounds relative to the offset so we can add a new offset later.
4171 IntPoint
boundsOffset(offset
.x
/ aQuantizeScale
.x
,
4172 offset
.y
/ aQuantizeScale
.y
);
4173 mBounds
-= boundsOffset
;
4174 mFullBounds
-= boundsOffset
;
4175 for (size_t i
= 0; i
< aBuffer
.mNumGlyphs
; i
++) {
4176 Glyph
& dst
= glyphs
[i
];
4177 const Glyph
& src
= aBuffer
.mGlyphs
[i
];
4178 dst
.mIndex
= src
.mIndex
;
4179 dst
.mPosition
= Point(QuantizePosition(aTransform
, offset
, src
.mPosition
));
4181 mBuffer
.mGlyphs
= glyphs
;
4182 mBuffer
.mNumGlyphs
= aBuffer
.mNumGlyphs
;
4185 GlyphCacheEntry::~GlyphCacheEntry() { delete[] mBuffer
.mGlyphs
; }
4187 // Attempt to find a matching entry in the glyph cache. The caller should check
4188 // whether the contained texture handle is valid to determine if it will need to
4189 // render the text run or just reuse the cached texture.
4190 already_AddRefed
<GlyphCacheEntry
> GlyphCache::FindEntry(
4191 const GlyphBuffer
& aBuffer
, const DeviceColor
& aColor
,
4192 const Matrix
& aTransform
, const IntPoint
& aQuantizeScale
,
4193 const IntRect
& aClipRect
, HashNumber aHash
,
4194 const StrokeOptions
* aStrokeOptions
) {
4195 IntPoint offset
= QuantizeOffset(aTransform
, aQuantizeScale
, aBuffer
);
4196 IntPoint
boundsOffset(offset
.x
/ aQuantizeScale
.x
,
4197 offset
.y
/ aQuantizeScale
.y
);
4198 for (const RefPtr
<GlyphCacheEntry
>& entry
: GetChain(aHash
)) {
4199 if (entry
->MatchesGlyphs(aBuffer
, aColor
, aTransform
, offset
, boundsOffset
,
4200 aClipRect
, aHash
, aStrokeOptions
)) {
4201 return do_AddRef(entry
);
4207 // Insert a new entry in the glyph cache.
4208 already_AddRefed
<GlyphCacheEntry
> GlyphCache::InsertEntry(
4209 const GlyphBuffer
& aBuffer
, const DeviceColor
& aColor
,
4210 const Matrix
& aTransform
, const IntPoint
& aQuantizeScale
,
4211 const IntRect
& aBounds
, const IntRect
& aFullBounds
, HashNumber aHash
,
4212 const StrokeOptions
* aStrokeOptions
) {
4213 StoredStrokeOptions
* strokeOptions
= nullptr;
4214 if (aStrokeOptions
) {
4215 strokeOptions
= aStrokeOptions
->Clone();
4216 if (!strokeOptions
) {
4220 RefPtr
<GlyphCacheEntry
> entry
=
4221 new GlyphCacheEntry(aBuffer
, aColor
, aTransform
, aQuantizeScale
, aBounds
,
4222 aFullBounds
, aHash
, strokeOptions
);
4224 return entry
.forget();
4227 GlyphCache::GlyphCache(ScaledFont
* aFont
) : mFont(aFont
) {}
4229 static void ReleaseGlyphCache(void* aPtr
) {
4230 delete static_cast<GlyphCache
*>(aPtr
);
4233 void DrawTargetWebgl::SetPermitSubpixelAA(bool aPermitSubpixelAA
) {
4234 DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA
);
4235 mSkia
->SetPermitSubpixelAA(aPermitSubpixelAA
);
4238 // Check for any color glyphs contained within a rasterized BGRA8 text result.
4239 static bool CheckForColorGlyphs(const RefPtr
<SourceSurface
>& aSurface
) {
4240 if (aSurface
->GetFormat() != SurfaceFormat::B8G8R8A8
) {
4243 RefPtr
<DataSourceSurface
> dataSurf
= aSurface
->GetDataSurface();
4247 DataSourceSurface::ScopedMap
map(dataSurf
, DataSourceSurface::READ
);
4248 if (!map
.IsMapped()) {
4251 IntSize size
= dataSurf
->GetSize();
4252 const uint8_t* data
= map
.GetData();
4253 int32_t stride
= map
.GetStride();
4254 for (int y
= 0; y
< size
.height
; y
++) {
4255 const uint32_t* x
= (const uint32_t*)data
;
4256 const uint32_t* end
= x
+ size
.width
;
4257 for (; x
< end
; x
++) {
4258 // Verify if all components are the same as for premultiplied grayscale.
4259 uint32_t color
= *x
;
4260 uint32_t gray
= color
& 0xFF;
4263 if (color
!= gray
) return true;
4270 // Draws glyphs to the WebGL target by trying to generate a cached texture for
4271 // the text run that can be subsequently reused to quickly render the text run
4272 // without using any software surfaces.
4273 bool SharedContextWebgl::DrawGlyphsAccel(ScaledFont
* aFont
,
4274 const GlyphBuffer
& aBuffer
,
4275 const Pattern
& aPattern
,
4276 const DrawOptions
& aOptions
,
4277 const StrokeOptions
* aStrokeOptions
,
4278 bool aUseSubpixelAA
) {
4279 // Whether the font may use bitmaps. If so, we need to render the glyphs with
4280 // color as grayscale bitmaps will use the color while color emoji will not,
4281 // with no easy way to know ahead of time. We currently have to check the
4282 // rasterized result to see if there are any color glyphs. To render subpixel
4283 // masks, we need to know that the rasterized result actually represents a
4284 // subpixel mask rather than try to interpret it as a normal RGBA result such
4285 // as for color emoji.
4286 bool useBitmaps
= !aStrokeOptions
&& aFont
->MayUseBitmaps() &&
4287 aOptions
.mCompositionOp
!= CompositionOp::OP_CLEAR
;
4289 // Look for an existing glyph cache on the font. If not there, create it.
4291 static_cast<GlyphCache
*>(aFont
->GetUserData(&mGlyphCacheKey
));
4293 cache
= new GlyphCache(aFont
);
4294 aFont
->AddUserData(&mGlyphCacheKey
, cache
, ReleaseGlyphCache
);
4295 mGlyphCaches
.insertFront(cache
);
4297 // Hash the incoming text run and looking for a matching entry.
4298 DeviceColor color
= aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
4299 ? DeviceColor(1, 1, 1, 1)
4300 : static_cast<const ColorPattern
&>(aPattern
).mColor
;
4302 // On macOS, depending on whether the text is classified as light-on-dark or
4303 // dark-on-light, we may end up with different amounts of dilation applied, so
4304 // we can't use the same mask in the two circumstances, or the glyphs will be
4305 // dilated incorrectly.
4307 useBitmaps
|| (color
.r
>= 0.33f
&& color
.g
>= 0.33f
&& color
.b
>= 0.33f
&&
4308 color
.r
+ color
.g
+ color
.b
>= 2.0f
);
4310 // On other platforms, we assume no color-dependent dilation.
4311 const bool lightOnDark
= true;
4313 // If the font has bitmaps, use the color directly. Otherwise, the texture
4314 // will hold a grayscale mask, so encode the key's subpixel and light-or-dark
4315 // state in the color.
4316 const Matrix
& currentTransform
= mCurrentTarget
->GetTransform();
4317 IntPoint quantizeScale
= QuantizeScale(aFont
, currentTransform
);
4318 Matrix quantizeTransform
= currentTransform
;
4319 quantizeTransform
.PostScale(quantizeScale
.x
, quantizeScale
.y
);
4321 GlyphCacheEntry::HashGlyphs(aBuffer
, quantizeTransform
, quantizeScale
);
4322 DeviceColor colorOrMask
=
4325 : DeviceColor::Mask(aUseSubpixelAA
? 1 : 0, lightOnDark
? 1 : 0);
4326 IntRect
clipRect(IntPoint(), mViewportSize
);
4327 RefPtr
<GlyphCacheEntry
> entry
=
4328 cache
->FindEntry(aBuffer
, colorOrMask
, quantizeTransform
, quantizeScale
,
4329 clipRect
, hash
, aStrokeOptions
);
4331 // For small text runs, bounds computations can be expensive relative to the
4332 // cost of looking up a cache result. Avoid doing local bounds computations
4333 // until actually inserting the entry into the cache.
4334 Maybe
<Rect
> bounds
= mCurrentTarget
->mSkia
->GetGlyphLocalBounds(
4335 aFont
, aBuffer
, aPattern
, aStrokeOptions
, aOptions
);
4339 // Transform the local bounds into device space so that we know how big
4340 // the cached texture will be.
4341 Rect xformBounds
= currentTransform
.TransformBounds(*bounds
);
4342 // Check if the transform flattens out the bounds before rounding.
4343 if (xformBounds
.IsEmpty()) {
4346 IntRect fullBounds
= RoundedOut(currentTransform
.TransformBounds(*bounds
));
4347 IntRect clipBounds
= fullBounds
.Intersect(clipRect
);
4348 // Check if the bounds are completely clipped out.
4349 if (clipBounds
.IsEmpty()) {
4352 entry
= cache
->InsertEntry(aBuffer
, colorOrMask
, quantizeTransform
,
4353 quantizeScale
, clipBounds
, fullBounds
, hash
,
4360 // The bounds of the entry may have a different transform offset from the
4361 // bounds of the currently drawn text run. The entry bounds are relative to
4362 // the entry's quantized offset already, so just move the bounds to the new
4364 IntRect intBounds
= entry
->GetBounds();
4365 IntPoint newOffset
=
4366 QuantizeOffset(quantizeTransform
, quantizeScale
, aBuffer
);
4368 IntPoint(newOffset
.x
/ quantizeScale
.x
, newOffset
.y
/ quantizeScale
.y
);
4369 // Ensure there is a clear border around the text. This must be applied only
4370 // after clipping so that we always have some border texels for filtering.
4371 intBounds
.Inflate(2);
4373 RefPtr
<TextureHandle
> handle
= entry
->GetHandle();
4374 if (handle
&& handle
->IsValid()) {
4375 // If there is an entry with a valid cached texture handle, then try
4376 // to draw with that. If that for some reason failed, then fall back
4377 // to using the Skia target as that means we were preventing from
4378 // drawing to the WebGL context based on something other than the
4380 SurfacePattern
pattern(nullptr, ExtendMode::CLAMP
,
4381 Matrix::Translation(intBounds
.TopLeft()));
4382 if (DrawRectAccel(Rect(intBounds
), pattern
, aOptions
,
4383 useBitmaps
? Nothing() : Some(color
), &handle
, false,
4390 // If we get here, either there wasn't a cached texture handle or it
4391 // wasn't valid. Render the text run into a temporary target.
4392 RefPtr
<DrawTargetSkia
> textDT
= new DrawTargetSkia
;
4393 if (textDT
->Init(intBounds
.Size(),
4394 lightOnDark
&& !useBitmaps
&& !aUseSubpixelAA
4396 : SurfaceFormat::B8G8R8A8
)) {
4398 // If rendering dark-on-light text, we need to clear the background to
4399 // white while using an opaque alpha value to allow this.
4400 textDT
->FillRect(Rect(IntRect(IntPoint(), intBounds
.Size())),
4401 ColorPattern(DeviceColor(1, 1, 1, 1)),
4402 DrawOptions(1.0f
, CompositionOp::OP_OVER
));
4404 textDT
->SetTransform(currentTransform
*
4405 Matrix::Translation(-intBounds
.TopLeft()));
4406 textDT
->SetPermitSubpixelAA(aUseSubpixelAA
);
4407 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
,
4408 aOptions
.mAntialiasMode
);
4409 // If bitmaps might be used, then we have to supply the color, as color
4410 // emoji may ignore it while grayscale bitmaps may use it, with no way to
4411 // know ahead of time. Otherwise, assume the output will be a mask and
4412 // just render it white to determine intensity. Depending on whether the
4413 // text is light or dark, we render white or black text respectively.
4414 ColorPattern
colorPattern(
4415 useBitmaps
? color
: DeviceColor::Mask(lightOnDark
? 1 : 0, 1));
4416 if (aStrokeOptions
) {
4417 textDT
->StrokeGlyphs(aFont
, aBuffer
, colorPattern
, *aStrokeOptions
,
4420 textDT
->FillGlyphs(aFont
, aBuffer
, colorPattern
, drawOptions
);
4423 uint8_t* data
= nullptr;
4426 SurfaceFormat format
= SurfaceFormat::UNKNOWN
;
4427 if (!textDT
->LockBits(&data
, &size
, &stride
, &format
)) {
4430 uint8_t* row
= data
;
4431 for (int y
= 0; y
< size
.height
; ++y
) {
4433 for (int x
= 0; x
< size
.width
; ++x
) {
4434 // If rendering dark-on-light text, we need to invert the final mask
4435 // so that it is in the expected white text on transparent black
4436 // format. The alpha will be initialized to the largest of the
4438 px
[0] = 255 - px
[0];
4439 px
[1] = 255 - px
[1];
4440 px
[2] = 255 - px
[2];
4441 px
[3] = std::max(px
[0], std::max(px
[1], px
[2]));
4446 textDT
->ReleaseBits(data
);
4448 RefPtr
<SourceSurface
> textSurface
= textDT
->Snapshot();
4450 // If we don't expect the text surface to contain color glyphs
4451 // such as from subpixel AA, then do one final check to see if
4452 // any ended up in the result. If not, extract the alpha values
4453 // from the surface so we can render it as a mask.
4454 if (textSurface
->GetFormat() != SurfaceFormat::A8
&&
4455 !CheckForColorGlyphs(textSurface
)) {
4456 textSurface
= ExtractAlpha(textSurface
, !useBitmaps
);
4458 // Failed extracting alpha for the text surface...
4462 // Attempt to upload the rendered text surface into a texture
4463 // handle and draw it.
4464 SurfacePattern
pattern(textSurface
, ExtendMode::CLAMP
,
4465 Matrix::Translation(intBounds
.TopLeft()));
4466 if (DrawRectAccel(Rect(intBounds
), pattern
, aOptions
,
4467 useBitmaps
? Nothing() : Some(color
), &handle
, false,
4470 // If drawing succeeded, then the text surface was uploaded to
4471 // a texture handle. Assign it to the glyph cache entry.
4472 entry
->Link(handle
);
4474 // If drawing failed, remove the entry from the cache.
4484 void DrawTargetWebgl::FillGlyphs(ScaledFont
* aFont
, const GlyphBuffer
& aBuffer
,
4485 const Pattern
& aPattern
,
4486 const DrawOptions
& aOptions
) {
4487 if (!aFont
|| !aBuffer
.mNumGlyphs
) {
4491 bool useSubpixelAA
= ShouldUseSubpixelAA(aFont
, aOptions
);
4493 if (mWebglValid
&& SupportsDrawOptions(aOptions
) &&
4494 aPattern
.GetType() == PatternType::COLOR
&& PrepareContext() &&
4495 mSharedContext
->DrawGlyphsAccel(aFont
, aBuffer
, aPattern
, aOptions
,
4496 nullptr, useSubpixelAA
)) {
4500 // If not able to cache the text run to a texture, then just fall back to
4501 // drawing with the Skia target.
4502 if (useSubpixelAA
) {
4503 // Subpixel AA does not support layering because the subpixel masks can't
4504 // blend with the over op.
4507 MarkSkiaChanged(aOptions
);
4509 mSkia
->FillGlyphs(aFont
, aBuffer
, aPattern
, aOptions
);
4512 // Attempts to read the contents of the WebGL context into the Skia target.
4513 bool DrawTargetWebgl::ReadIntoSkia() {
4517 bool didReadback
= false;
4519 uint8_t* data
= nullptr;
4522 SurfaceFormat format
;
4524 // If the WebGL target is still clear, then just clear the Skia target.
4525 mSkia
->DetachAllSnapshots();
4526 mSkiaNoClip
->FillRect(Rect(mSkiaNoClip
->GetRect()), GetClearPattern(),
4527 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
4529 // If there's no existing snapshot and we can successfully map the Skia
4530 // target for reading, then try to read into that.
4531 if (!mSnapshot
&& mSkia
->LockBits(&data
, &size
, &stride
, &format
)) {
4532 (void)ReadInto(data
, stride
);
4533 mSkia
->ReleaseBits(data
);
4534 } else if (RefPtr
<SourceSurface
> snapshot
= Snapshot()) {
4535 // Otherwise, fall back to getting a snapshot from WebGL if available
4536 // and then copying that to Skia.
4537 mSkia
->CopySurface(snapshot
, GetRect(), IntPoint(0, 0));
4543 // The Skia data is flat after reading, so disable any layering.
4548 // Reads data from the WebGL context and blends it with the current Skia layer.
4549 void DrawTargetWebgl::FlattenSkia() {
4550 if (!mSkiaValid
|| !mSkiaLayer
) {
4554 if (mSkiaLayerClear
) {
4555 // If the WebGL target is clear, then there is nothing to blend.
4558 if (RefPtr
<DataSourceSurface
> base
= ReadSnapshot()) {
4559 mSkia
->DetachAllSnapshots();
4560 mSkiaNoClip
->DrawSurface(base
, Rect(GetRect()), Rect(GetRect()),
4561 DrawSurfaceOptions(SamplingFilter::POINT
),
4562 DrawOptions(1.f
, CompositionOp::OP_DEST_OVER
));
4566 // Attempts to draw the contents of the Skia target into the WebGL context.
4567 bool DrawTargetWebgl::FlushFromSkia() {
4568 // If the WebGL context has been lost, then mark it as invalid and fail.
4569 if (mSharedContext
->IsContextLost()) {
4570 mWebglValid
= false;
4573 // The WebGL target is already valid, so there is nothing to do.
4577 // Ensure that DrawRect doesn't recursively call into FlushFromSkia. If
4578 // the Skia target isn't valid, then it doesn't matter what is in the the
4579 // WebGL target either, so only try to blend if there is a valid Skia target.
4582 AutoRestoreContext
restore(this);
4584 // If the Skia target is clear, then there is no need to use a snapshot.
4585 // Directly clear the WebGL target instead.
4587 if (!DrawRect(Rect(GetRect()), GetClearPattern(),
4588 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
), Nothing(),
4589 nullptr, false, false, true)) {
4590 mWebglValid
= false;
4596 RefPtr
<SourceSurface
> skiaSnapshot
= mSkia
->Snapshot();
4597 if (!skiaSnapshot
) {
4598 // There's a valid Skia target to draw to, but for some reason there is
4599 // no available snapshot, so just keep using the Skia target.
4600 mWebglValid
= false;
4604 // If there is no layer, then just upload it directly.
4606 if (PrepareContext(false) && MarkChanged()) {
4607 if (RefPtr
<DataSourceSurface
> data
= skiaSnapshot
->GetDataSurface()) {
4608 mSharedContext
->UploadSurface(data
, mFormat
, GetRect(), IntPoint(),
4609 false, false, mTex
);
4613 // Failed to upload the Skia snapshot.
4614 mWebglValid
= false;
4618 SurfacePattern
pattern(skiaSnapshot
, ExtendMode::CLAMP
);
4619 // If there is a layer, blend the snapshot with the WebGL context.
4620 if (!DrawRect(Rect(GetRect()), pattern
,
4621 DrawOptions(1.0f
, CompositionOp::OP_OVER
), Nothing(),
4622 &mSnapshotTexture
, false, false, true, true)) {
4623 // If accelerated drawing failed for some reason, then leave the Skia
4624 // target unchanged.
4625 mWebglValid
= false;
4632 void DrawTargetWebgl::UsageProfile::BeginFrame() {
4633 // Reset the usage profile counters for the new frame.
4642 void DrawTargetWebgl::UsageProfile::EndFrame() {
4643 bool failed
= false;
4644 // If we hit a complete fallback to software rendering, or if cache misses
4645 // were more than cutoff ratio of all requests, then we consider the frame as
4646 // having failed performance profiling.
4648 StaticPrefs::gfx_canvas_accelerated_profile_cache_miss_ratio();
4649 if (mFallbacks
> 0 ||
4650 float(mCacheMisses
+ mReadbacks
+ mLayers
) >
4651 cacheRatio
* float(mCacheMisses
+ mCacheHits
+ mUncachedDraws
+
4652 mReadbacks
+ mLayers
)) {
4661 bool DrawTargetWebgl::UsageProfile::RequiresRefresh() const {
4662 // If we've rendered at least the required number of frames for a profile and
4663 // more than the cutoff ratio of frames did not meet performance criteria,
4664 // then we should stop using an accelerated canvas.
4665 uint32_t profileFrames
= StaticPrefs::gfx_canvas_accelerated_profile_frames();
4666 if (!profileFrames
|| mFrameCount
< profileFrames
) {
4670 StaticPrefs::gfx_canvas_accelerated_profile_fallback_ratio();
4671 return mFailedFrames
> failRatio
* mFrameCount
;
4674 void SharedContextWebgl::CachePrefs() {
4675 uint32_t capacity
= StaticPrefs::gfx_canvas_accelerated_gpu_path_size() << 20;
4676 if (capacity
!= mPathVertexCapacity
) {
4677 mPathVertexCapacity
= capacity
;
4679 mPathCache
->ClearVertexRanges();
4681 if (mPathVertexBuffer
) {
4682 ResetPathVertexBuffer();
4686 mPathMaxComplexity
=
4687 StaticPrefs::gfx_canvas_accelerated_gpu_path_complexity();
4689 mPathAAStroke
= StaticPrefs::gfx_canvas_accelerated_aa_stroke_enabled();
4690 mPathWGRStroke
= StaticPrefs::gfx_canvas_accelerated_stroke_to_fill_path();
4693 // For use within CanvasRenderingContext2D, called on BorrowDrawTarget.
4694 void DrawTargetWebgl::BeginFrame(bool aInvalidContents
) {
4695 // If still rendering into the Skia target, switch back to the WebGL
4698 if (aInvalidContents
) {
4699 // If nothing needs to persist, just mark the WebGL context valid.
4701 // Even if the Skia framebuffer is marked clear, since the WebGL
4702 // context is not valid, its contents may be out-of-date and not
4703 // necessarily clear.
4709 // Check if we need to clear out any cached because of memory pressure.
4710 mSharedContext
->ClearCachesIfNecessary();
4711 // Cache any prefs for the frame.
4712 mSharedContext
->CachePrefs();
4713 mProfile
.BeginFrame();
4716 // For use within CanvasRenderingContext2D, called on ReturnDrawTarget.
4717 void DrawTargetWebgl::EndFrame() {
4718 if (StaticPrefs::gfx_canvas_accelerated_debug()) {
4719 // Draw a green rectangle in the upper right corner to indicate
4721 IntRect corner
= IntRect(mSize
.width
- 16, 0, 16, 16).Intersect(GetRect());
4722 DrawRect(Rect(corner
), ColorPattern(DeviceColor(0.0f
, 1.0f
, 0.0f
, 1.0f
)),
4723 DrawOptions(), Nothing(), nullptr, false, false);
4725 mProfile
.EndFrame();
4726 // Ensure we're not somehow using more than the allowed texture memory.
4727 mSharedContext
->PruneTextureMemory();
4728 // Signal that we're done rendering the frame in case no present occurs.
4729 mSharedContext
->mWebgl
->EndOfFrame();
4730 // Check if we need to clear out any cached because of memory pressure.
4731 mSharedContext
->ClearCachesIfNecessary();
4734 bool DrawTargetWebgl::CopyToSwapChain(
4735 layers::TextureType aTextureType
, layers::RemoteTextureId aId
,
4736 layers::RemoteTextureOwnerId aOwnerId
,
4737 layers::RemoteTextureOwnerClient
* aOwnerClient
) {
4738 if (!mWebglValid
&& !FlushFromSkia()) {
4742 // Copy and swizzle the WebGL framebuffer to the swap chain front buffer.
4743 webgl::SwapChainOptions options
;
4744 options
.bgra
= true;
4745 // Allow async present to be toggled on for accelerated Canvas2D
4746 // independent of WebGL via pref.
4747 options
.forceAsyncPresent
=
4748 StaticPrefs::gfx_canvas_accelerated_async_present();
4749 options
.remoteTextureId
= aId
;
4750 options
.remoteTextureOwnerId
= aOwnerId
;
4751 return mSharedContext
->mWebgl
->CopyToSwapChain(mFramebuffer
, aTextureType
,
4752 options
, aOwnerClient
);
4755 already_AddRefed
<DrawTarget
> DrawTargetWebgl::CreateSimilarDrawTarget(
4756 const IntSize
& aSize
, SurfaceFormat aFormat
) const {
4757 return mSkia
->CreateSimilarDrawTarget(aSize
, aFormat
);
4760 bool DrawTargetWebgl::CanCreateSimilarDrawTarget(const IntSize
& aSize
,
4761 SurfaceFormat aFormat
) const {
4762 return mSkia
->CanCreateSimilarDrawTarget(aSize
, aFormat
);
4765 RefPtr
<DrawTarget
> DrawTargetWebgl::CreateClippedDrawTarget(
4766 const Rect
& aBounds
, SurfaceFormat aFormat
) {
4767 return mSkia
->CreateClippedDrawTarget(aBounds
, aFormat
);
4770 already_AddRefed
<SourceSurface
> DrawTargetWebgl::CreateSourceSurfaceFromData(
4771 unsigned char* aData
, const IntSize
& aSize
, int32_t aStride
,
4772 SurfaceFormat aFormat
) const {
4773 return mSkia
->CreateSourceSurfaceFromData(aData
, aSize
, aStride
, aFormat
);
4776 already_AddRefed
<SourceSurface
>
4777 DrawTargetWebgl::CreateSourceSurfaceFromNativeSurface(
4778 const NativeSurface
& aSurface
) const {
4779 return mSkia
->CreateSourceSurfaceFromNativeSurface(aSurface
);
4782 already_AddRefed
<SourceSurface
> DrawTargetWebgl::OptimizeSourceSurface(
4783 SourceSurface
* aSurface
) const {
4784 if (aSurface
->GetType() == SurfaceType::WEBGL
) {
4785 return do_AddRef(aSurface
);
4787 return mSkia
->OptimizeSourceSurface(aSurface
);
4790 already_AddRefed
<SourceSurface
>
4791 DrawTargetWebgl::OptimizeSourceSurfaceForUnknownAlpha(
4792 SourceSurface
* aSurface
) const {
4793 return mSkia
->OptimizeSourceSurfaceForUnknownAlpha(aSurface
);
4796 already_AddRefed
<GradientStops
> DrawTargetWebgl::CreateGradientStops(
4797 GradientStop
* aStops
, uint32_t aNumStops
, ExtendMode aExtendMode
) const {
4798 return mSkia
->CreateGradientStops(aStops
, aNumStops
, aExtendMode
);
4801 already_AddRefed
<FilterNode
> DrawTargetWebgl::CreateFilter(FilterType aType
) {
4802 return mSkia
->CreateFilter(aType
);
4805 void DrawTargetWebgl::DrawFilter(FilterNode
* aNode
, const Rect
& aSourceRect
,
4806 const Point
& aDestPoint
,
4807 const DrawOptions
& aOptions
) {
4808 MarkSkiaChanged(aOptions
);
4809 mSkia
->DrawFilter(aNode
, aSourceRect
, aDestPoint
, aOptions
);
4812 bool DrawTargetWebgl::Draw3DTransformedSurface(SourceSurface
* aSurface
,
4813 const Matrix4x4
& aMatrix
) {
4815 return mSkia
->Draw3DTransformedSurface(aSurface
, aMatrix
);
4818 void DrawTargetWebgl::PushLayer(bool aOpaque
, Float aOpacity
,
4819 SourceSurface
* aMask
,
4820 const Matrix
& aMaskTransform
,
4821 const IntRect
& aBounds
, bool aCopyBackground
) {
4822 PushLayerWithBlend(aOpaque
, aOpacity
, aMask
, aMaskTransform
, aBounds
,
4823 aCopyBackground
, CompositionOp::OP_OVER
);
4826 void DrawTargetWebgl::PushLayerWithBlend(bool aOpaque
, Float aOpacity
,
4827 SourceSurface
* aMask
,
4828 const Matrix
& aMaskTransform
,
4829 const IntRect
& aBounds
,
4830 bool aCopyBackground
,
4831 CompositionOp aCompositionOp
) {
4832 MarkSkiaChanged(DrawOptions(aOpacity
, aCompositionOp
));
4833 mSkia
->PushLayerWithBlend(aOpaque
, aOpacity
, aMask
, aMaskTransform
, aBounds
,
4834 aCopyBackground
, aCompositionOp
);
4836 SetPermitSubpixelAA(mSkia
->GetPermitSubpixelAA());
4839 void DrawTargetWebgl::PopLayer() {
4840 MOZ_ASSERT(mSkiaValid
);
4841 MOZ_ASSERT(mLayerDepth
> 0);
4844 SetPermitSubpixelAA(mSkia
->GetPermitSubpixelAA());
4847 } // namespace mozilla::gfx