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