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"
41 # include "mozilla/gfx/ScaledFontMac.h"
44 namespace mozilla::gfx
{
46 BackingTexture::BackingTexture(const IntSize
& aSize
, SurfaceFormat aFormat
,
47 const RefPtr
<WebGLTexture
>& aTexture
)
48 : mSize(aSize
), mFormat(aFormat
), mTexture(aTexture
) {}
51 // Work around buggy ANGLE/D3D drivers that may copy blocks of pixels outside
52 // the row length. Extra space is reserved at the end of each row up to stride
53 // alignment. This does not affect standalone textures.
54 static const Etagere::AllocatorOptions kR8AllocatorOptions
= {16, 1, 1, 0};
57 SharedTexture::SharedTexture(const IntSize
& aSize
, SurfaceFormat aFormat
,
58 const RefPtr
<WebGLTexture
>& aTexture
)
59 : BackingTexture(aSize
, aFormat
, aTexture
),
62 aFormat
== SurfaceFormat::A8
63 ? Etagere::etagere_atlas_allocator_with_options(
64 aSize
.width
, aSize
.height
, &kR8AllocatorOptions
)
67 Etagere::etagere_atlas_allocator_new(aSize
.width
, aSize
.height
)) {
70 SharedTexture::~SharedTexture() {
71 if (mAtlasAllocator
) {
72 Etagere::etagere_atlas_allocator_delete(mAtlasAllocator
);
73 mAtlasAllocator
= nullptr;
77 SharedTextureHandle::SharedTextureHandle(Etagere::AllocationId aId
,
78 const IntRect
& aBounds
,
79 SharedTexture
* aTexture
)
80 : mAllocationId(aId
), mBounds(aBounds
), mTexture(aTexture
) {}
82 already_AddRefed
<SharedTextureHandle
> SharedTexture::Allocate(
83 const IntSize
& aSize
) {
84 Etagere::Allocation alloc
= {{0, 0, 0, 0}, Etagere::INVALID_ALLOCATION_ID
};
85 if (!mAtlasAllocator
||
86 !Etagere::etagere_atlas_allocator_allocate(mAtlasAllocator
, aSize
.width
,
87 aSize
.height
, &alloc
) ||
88 alloc
.id
== Etagere::INVALID_ALLOCATION_ID
) {
91 RefPtr
<SharedTextureHandle
> handle
= new SharedTextureHandle(
93 IntRect(IntPoint(alloc
.rectangle
.min_x
, alloc
.rectangle
.min_y
), aSize
),
95 return handle
.forget();
98 bool SharedTexture::Free(SharedTextureHandle
& aHandle
) {
99 if (aHandle
.mTexture
!= this) {
102 if (aHandle
.mAllocationId
!= Etagere::INVALID_ALLOCATION_ID
) {
103 if (mAtlasAllocator
) {
104 Etagere::etagere_atlas_allocator_deallocate(mAtlasAllocator
,
105 aHandle
.mAllocationId
);
107 aHandle
.mAllocationId
= Etagere::INVALID_ALLOCATION_ID
;
112 StandaloneTexture::StandaloneTexture(const IntSize
& aSize
,
113 SurfaceFormat aFormat
,
114 const RefPtr
<WebGLTexture
>& aTexture
)
115 : BackingTexture(aSize
, aFormat
, aTexture
) {}
117 DrawTargetWebgl::DrawTargetWebgl() = default;
119 inline void SharedContextWebgl::ClearLastTexture(bool aFullClear
) {
120 mLastTexture
= nullptr;
122 mLastClipMask
= nullptr;
126 // Attempts to clear the snapshot state. If the snapshot is only referenced by
127 // this target, then it should simply be destroyed. If it is a WebGL surface in
128 // use by something else, then special cleanup such as reusing the texture or
129 // copy-on-write may be possible.
130 void DrawTargetWebgl::ClearSnapshot(bool aCopyOnWrite
, bool aNeedHandle
) {
134 mSharedContext
->ClearLastTexture();
135 RefPtr
<SourceSurfaceWebgl
> snapshot
= mSnapshot
.forget();
136 if (snapshot
->hasOneRef()) {
140 // WebGL snapshots must be notified that the framebuffer contents will be
141 // changing so that it can copy the data.
142 snapshot
->DrawTargetWillChange(aNeedHandle
);
144 // If not copying, then give the backing texture to the surface for reuse.
145 snapshot
->GiveTexture(
146 mSharedContext
->WrapSnapshot(GetSize(), GetFormat(), mTex
.forget()));
150 DrawTargetWebgl::~DrawTargetWebgl() {
151 ClearSnapshot(false);
152 if (mSharedContext
) {
153 // Force any Skia snapshots to copy the shmem before it deallocs.
155 mSkia
->DetachAllSnapshots();
157 mSharedContext
->ClearLastTexture(true);
159 mFramebuffer
= nullptr;
161 mSharedContext
->mDrawTargetCount
--;
165 SharedContextWebgl::SharedContextWebgl() = default;
167 SharedContextWebgl::~SharedContextWebgl() {
168 // Detect context loss before deletion.
171 mWebgl
->ActiveTexture(0);
173 if (mWGRPathBuilder
) {
174 WGR::wgr_builder_release(mWGRPathBuilder
);
175 mWGRPathBuilder
= nullptr;
178 UnlinkSurfaceTextures();
182 gl::GLContext
* SharedContextWebgl::GetGLContext() {
183 return mWebgl
? mWebgl
->GL() : nullptr;
186 void SharedContextWebgl::EnterTlsScope() {
187 if (mTlsScope
.isSome()) {
190 if (gl::GLContext
* gl
= GetGLContext()) {
191 mTlsScope
= Some(gl
->mUseTLSIsCurrent
);
192 gl::GLContext::InvalidateCurrentContext();
193 gl
->mUseTLSIsCurrent
= true;
197 void SharedContextWebgl::ExitTlsScope() {
198 if (mTlsScope
.isNothing()) {
201 if (gl::GLContext
* gl
= GetGLContext()) {
202 gl
->mUseTLSIsCurrent
= mTlsScope
.value();
204 mTlsScope
= Nothing();
207 // Remove any SourceSurface user data associated with this TextureHandle.
208 inline void SharedContextWebgl::UnlinkSurfaceTexture(
209 const RefPtr
<TextureHandle
>& aHandle
) {
210 if (RefPtr
<SourceSurface
> surface
= aHandle
->GetSurface()) {
211 // Ensure any WebGL snapshot textures get unlinked.
212 if (surface
->GetType() == SurfaceType::WEBGL
) {
213 static_cast<SourceSurfaceWebgl
*>(surface
.get())->OnUnlinkTexture(this);
215 surface
->RemoveUserData(aHandle
->IsShadow() ? &mShadowTextureKey
216 : &mTextureHandleKey
);
220 // Unlinks TextureHandles from any SourceSurface user data.
221 void SharedContextWebgl::UnlinkSurfaceTextures() {
222 for (RefPtr
<TextureHandle
> handle
= mTextureHandles
.getFirst(); handle
;
223 handle
= handle
->getNext()) {
224 UnlinkSurfaceTexture(handle
);
228 // Unlinks GlyphCaches from any ScaledFont user data.
229 void SharedContextWebgl::UnlinkGlyphCaches() {
230 GlyphCache
* cache
= mGlyphCaches
.getFirst();
232 ScaledFont
* font
= cache
->GetFont();
233 // Access the next cache before removing the user data, as it might destroy
235 cache
= cache
->getNext();
236 font
->RemoveUserData(&mGlyphCacheKey
);
240 void SharedContextWebgl::OnMemoryPressure() { mShouldClearCaches
= true; }
242 void SharedContextWebgl::ClearCaches() {
244 ClearCachesIfNecessary();
247 // Clear out the entire list of texture handles from any source.
248 void SharedContextWebgl::ClearAllTextures() {
249 while (!mTextureHandles
.isEmpty()) {
250 PruneTextureHandle(mTextureHandles
.popLast());
251 --mNumTextureHandles
;
255 // Scan through the shared texture pages looking for any that are empty and
257 void SharedContextWebgl::ClearEmptyTextureMemory() {
258 for (auto pos
= mSharedTextures
.begin(); pos
!= mSharedTextures
.end();) {
259 if (!(*pos
)->HasAllocatedHandles()) {
260 RefPtr
<SharedTexture
> shared
= *pos
;
261 size_t usedBytes
= shared
->UsedBytes();
262 mEmptyTextureMemory
-= usedBytes
;
263 mTotalTextureMemory
-= usedBytes
;
264 pos
= mSharedTextures
.erase(pos
);
271 // If there is a request to clear out the caches because of memory pressure,
272 // then first clear out all the texture handles in the texture cache. If there
273 // are still empty texture pages being kept around, then clear those too.
274 void SharedContextWebgl::ClearCachesIfNecessary() {
275 if (!mShouldClearCaches
.exchange(false)) {
278 mZeroBuffer
= nullptr;
280 if (mEmptyTextureMemory
) {
281 ClearEmptyTextureMemory();
286 // Try to initialize a new WebGL context. Verifies that the requested size does
287 // not exceed the available texture limits and that shader creation succeeded.
288 bool DrawTargetWebgl::Init(const IntSize
& size
, const SurfaceFormat format
,
289 const RefPtr
<SharedContextWebgl
>& aSharedContext
) {
290 MOZ_ASSERT(format
== SurfaceFormat::B8G8R8A8
||
291 format
== SurfaceFormat::B8G8R8X8
);
296 if (!aSharedContext
|| aSharedContext
->IsContextLost() ||
297 aSharedContext
->mDrawTargetCount
>=
298 StaticPrefs::gfx_canvas_accelerated_max_draw_target_count()) {
301 mSharedContext
= aSharedContext
;
302 mSharedContext
->mDrawTargetCount
++;
304 if (size_t(std::max(size
.width
, size
.height
)) >
305 mSharedContext
->mMaxTextureSize
) {
309 if (!CreateFramebuffer()) {
313 size_t byteSize
= layers::ImageDataSerializer::ComputeRGBBufferSize(
314 mSize
, SurfaceFormat::B8G8R8A8
);
319 size_t shmemSize
= mozilla::ipc::SharedMemory::PageAlignedSize(byteSize
);
320 if (NS_WARN_IF(shmemSize
> UINT32_MAX
)) {
321 MOZ_ASSERT_UNREACHABLE("Buffer too big?");
325 auto shmem
= MakeRefPtr
<mozilla::ipc::SharedMemory
>();
326 if (NS_WARN_IF(!shmem
->Create(shmemSize
)) ||
327 NS_WARN_IF(!shmem
->Map(shmemSize
))) {
331 mShmem
= std::move(shmem
);
332 mShmemSize
= shmemSize
;
334 mSkia
= new DrawTargetSkia
;
335 auto stride
= layers::ImageDataSerializer::ComputeRGBStride(
336 SurfaceFormat::B8G8R8A8
, size
.width
);
337 if (!mSkia
->Init(reinterpret_cast<uint8_t*>(mShmem
->Memory()), size
, stride
,
338 SurfaceFormat::B8G8R8A8
, true)) {
342 // Allocate an unclipped copy of the DT pointing to its data.
343 uint8_t* dtData
= nullptr;
345 int32_t dtStride
= 0;
346 SurfaceFormat dtFormat
= SurfaceFormat::UNKNOWN
;
347 if (!mSkia
->LockBits(&dtData
, &dtSize
, &dtStride
, &dtFormat
)) {
350 mSkiaNoClip
= new DrawTargetSkia
;
351 if (!mSkiaNoClip
->Init(dtData
, dtSize
, dtStride
, dtFormat
, true)) {
352 mSkia
->ReleaseBits(dtData
);
355 mSkia
->ReleaseBits(dtData
);
357 SetPermitSubpixelAA(IsOpaque(format
));
361 // If a non-recoverable error occurred that would stop the canvas from initing.
362 static Atomic
<bool> sContextInitError(false);
364 already_AddRefed
<SharedContextWebgl
> SharedContextWebgl::Create() {
365 // If context initialization would fail, don't even try to create a context.
366 if (sContextInitError
) {
369 RefPtr
<SharedContextWebgl
> sharedContext
= new SharedContextWebgl
;
370 if (!sharedContext
->Initialize()) {
373 return sharedContext
.forget();
376 bool SharedContextWebgl::Initialize() {
377 WebGLContextOptions options
= {};
378 options
.alpha
= true;
379 options
.depth
= false;
380 options
.stencil
= false;
381 options
.antialias
= false;
382 options
.preserveDrawingBuffer
= true;
383 options
.failIfMajorPerformanceCaveat
= false;
385 const bool resistFingerprinting
= nsContentUtils::ShouldResistFingerprinting(
386 "Fallback", RFPTarget::WebGLRenderCapability
);
387 const auto initDesc
= webgl::InitContextDesc
{
389 .resistFingerprinting
= resistFingerprinting
,
395 webgl::InitContextResult initResult
;
396 mWebgl
= WebGLContext::Create(nullptr, initDesc
, &initResult
);
398 // There was a non-recoverable error when trying to create a host context.
399 sContextInitError
= true;
403 if (mWebgl
->IsContextLost()) {
408 mMaxTextureSize
= initResult
.limits
.maxTex2dSize
;
411 mRasterizationTruncates
= initResult
.vendor
== gl::GLVendor::ATI
;
416 if (!CreateShaders()) {
417 // There was a non-recoverable error when trying to init shaders.
418 sContextInitError
= true;
423 mWGRPathBuilder
= WGR::wgr_new_builder();
428 inline void SharedContextWebgl::BlendFunc(GLenum aSrcFactor
,
430 mWebgl
->BlendFuncSeparate({}, aSrcFactor
, aDstFactor
, aSrcFactor
, aDstFactor
);
433 void SharedContextWebgl::SetBlendState(CompositionOp aOp
,
434 const Maybe
<DeviceColor
>& aColor
) {
435 if (aOp
== mLastCompositionOp
&& mLastBlendColor
== aColor
) {
438 mLastCompositionOp
= aOp
;
439 mLastBlendColor
= aColor
;
440 // AA is not supported for all composition ops, so switching blend modes may
441 // cause a toggle in AA state. Certain ops such as OP_SOURCE require output
442 // alpha that is blended separately from AA coverage. This would require two
443 // stage blending which can incur a substantial performance penalty, so to
444 // work around this currently we just disable AA for those ops.
446 // Map the composition op to a WebGL blend mode, if possible.
449 case CompositionOp::OP_OVER
:
451 // If a color is supplied, then we blend subpixel text.
452 mWebgl
->BlendColor(aColor
->b
, aColor
->g
, aColor
->r
, 1.0f
);
453 BlendFunc(LOCAL_GL_CONSTANT_COLOR
, LOCAL_GL_ONE_MINUS_SRC_COLOR
);
455 BlendFunc(LOCAL_GL_ONE
, LOCAL_GL_ONE_MINUS_SRC_ALPHA
);
458 case CompositionOp::OP_ADD
:
459 BlendFunc(LOCAL_GL_ONE
, LOCAL_GL_ONE
);
461 case CompositionOp::OP_ATOP
:
462 BlendFunc(LOCAL_GL_DST_ALPHA
, LOCAL_GL_ONE_MINUS_SRC_ALPHA
);
464 case CompositionOp::OP_SOURCE
:
466 // If a color is supplied, then we assume there is clipping or AA. This
467 // requires that we still use an over blend func with the clip/AA alpha,
468 // while filling the interior with the unaltered color. Normally this
469 // would require dual source blending, but we can emulate it with only
471 mWebgl
->BlendColor(aColor
->b
, aColor
->g
, aColor
->r
, aColor
->a
);
472 BlendFunc(LOCAL_GL_CONSTANT_COLOR
, LOCAL_GL_ONE_MINUS_SRC_COLOR
);
477 case CompositionOp::OP_CLEAR
:
478 // Assume the source is an alpha mask for clearing. Be careful to blend in
479 // the correct alpha if the target is opaque.
480 mWebgl
->BlendFuncSeparate(
481 {}, LOCAL_GL_ZERO
, LOCAL_GL_ONE_MINUS_SRC_ALPHA
,
482 IsOpaque(mCurrentTarget
->GetFormat()) ? LOCAL_GL_ONE
: LOCAL_GL_ZERO
,
483 LOCAL_GL_ONE_MINUS_SRC_ALPHA
);
490 mWebgl
->SetEnabled(LOCAL_GL_BLEND
, {}, enabled
);
493 // Ensure the WebGL framebuffer is set to the current target.
494 bool SharedContextWebgl::SetTarget(DrawTargetWebgl
* aDT
) {
495 if (!mWebgl
|| mWebgl
->IsContextLost()) {
498 if (aDT
!= mCurrentTarget
) {
499 mCurrentTarget
= aDT
;
501 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, aDT
->mFramebuffer
);
502 mViewportSize
= aDT
->GetSize();
503 mWebgl
->Viewport(0, 0, mViewportSize
.width
, mViewportSize
.height
);
509 // Replace the current clip rect with a new potentially-AA'd clip rect.
510 void SharedContextWebgl::SetClipRect(const Rect
& aClipRect
) {
511 // Only invalidate the clip rect if it actually changes.
512 if (!mClipAARect
.IsEqualEdges(aClipRect
)) {
513 mClipAARect
= aClipRect
;
514 // Store the integer-aligned bounds.
515 mClipRect
= RoundedOut(aClipRect
);
519 bool SharedContextWebgl::SetClipMask(const RefPtr
<WebGLTexture
>& aTex
) {
520 if (mLastClipMask
!= aTex
) {
524 mWebgl
->ActiveTexture(1);
525 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, aTex
);
526 mWebgl
->ActiveTexture(0);
527 mLastClipMask
= aTex
;
532 bool SharedContextWebgl::SetNoClipMask() {
534 return SetClipMask(mNoClipMask
);
539 mNoClipMask
= mWebgl
->CreateTexture();
543 mWebgl
->ActiveTexture(1);
544 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mNoClipMask
);
545 static const auto solidMask
=
546 std::array
<const uint8_t, 4>{0xFF, 0xFF, 0xFF, 0xFF};
547 mWebgl
->TexImage(0, LOCAL_GL_RGBA8
, {0, 0, 0},
548 {LOCAL_GL_RGBA
, LOCAL_GL_UNSIGNED_BYTE
},
549 {LOCAL_GL_TEXTURE_2D
,
551 gfxAlphaType::NonPremult
,
552 Some(Span
{solidMask
})});
553 InitTexParameters(mNoClipMask
, false);
554 mWebgl
->ActiveTexture(0);
555 mLastClipMask
= mNoClipMask
;
559 inline bool DrawTargetWebgl::ClipStack::operator==(
560 const DrawTargetWebgl::ClipStack
& aOther
) const {
561 // Verify the transform and bounds match.
562 if (!mTransform
.FuzzyEquals(aOther
.mTransform
) ||
563 !mRect
.IsEqualInterior(aOther
.mRect
)) {
566 // Verify the paths match.
568 return !aOther
.mPath
;
571 mPath
->GetBackendType() != aOther
.mPath
->GetBackendType()) {
574 if (mPath
->GetBackendType() != BackendType::SKIA
) {
575 return mPath
== aOther
.mPath
;
577 return static_cast<const PathSkia
*>(mPath
.get())->GetPath() ==
578 static_cast<const PathSkia
*>(aOther
.mPath
.get())->GetPath();
581 // If the clip region can't be approximated by a simple clip rect, then we need
582 // to generate a clip mask that can represent the clip region per-pixel. We
583 // render to the Skia target temporarily, transparent outside the clip region,
584 // opaque inside, and upload this to a texture that can be used by the shaders.
585 bool DrawTargetWebgl::GenerateComplexClipMask() {
586 if (!mClipChanged
|| (mClipMask
&& mCachedClipStack
== mClipStack
)) {
587 mClipChanged
= false;
588 // If the clip mask was already generated, use the cached mask and bounds.
589 mSharedContext
->SetClipMask(mClipMask
);
590 mSharedContext
->SetClipRect(mClipBounds
);
594 // If the Skia target is currently being used, then we can't render the mask
598 RefPtr
<WebGLContext
> webgl
= mSharedContext
->mWebgl
;
604 mClipMask
= webgl
->CreateTexture();
610 // Try to get the bounds of the clip to limit the size of the mask.
611 if (Maybe
<IntRect
> clip
= mSkia
->GetDeviceClipRect(true)) {
614 // If we can't get bounds, then just use the entire viewport.
615 mClipBounds
= GetRect();
617 mClipAARect
= Rect(mClipBounds
);
618 // If initializing the clip mask, then allocate the entire texture to ensure
619 // all pixels get filled with an empty mask regardless. Otherwise, restrict
620 // uploading to only the clip region.
621 RefPtr
<DrawTargetSkia
> dt
= new DrawTargetSkia
;
622 if (!dt
->Init(mClipBounds
.Size(), SurfaceFormat::A8
)) {
625 // Set the clip region and fill the entire inside of it
626 // with opaque white.
627 mCachedClipStack
.clear();
628 for (auto& clipStack
: mClipStack
) {
629 // Record the current state of the clip stack for this mask.
630 mCachedClipStack
.push_back(clipStack
);
632 Matrix(clipStack
.mTransform
).PostTranslate(-mClipBounds
.TopLeft()));
633 if (clipStack
.mPath
) {
634 dt
->PushClip(clipStack
.mPath
);
636 dt
->PushClipRect(clipStack
.mRect
);
639 dt
->SetTransform(Matrix::Translation(-mClipBounds
.TopLeft()));
640 dt
->FillRect(Rect(mClipBounds
), ColorPattern(DeviceColor(1, 1, 1, 1)));
641 // Bind the clip mask for uploading. This is done on texture unit 0 so that
642 // we can work around an Windows Intel driver bug. If done on texture unit 1,
643 // the driver doesn't notice that the texture contents was modified. Force a
644 // re-latch by binding the texture on texture unit 1 only after modification.
645 webgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mClipMask
);
647 mSharedContext
->InitTexParameters(mClipMask
, false);
649 RefPtr
<DataSourceSurface
> data
;
650 if (RefPtr
<SourceSurface
> snapshot
= dt
->Snapshot()) {
651 data
= snapshot
->GetDataSurface();
653 // Finally, upload the texture data and initialize texture storage if
655 if (init
&& mClipBounds
.Size() != mSize
) {
656 mSharedContext
->UploadSurface(nullptr, SurfaceFormat::A8
, GetRect(),
657 IntPoint(), true, true);
660 mSharedContext
->UploadSurface(data
, SurfaceFormat::A8
,
661 IntRect(IntPoint(), mClipBounds
.Size()),
662 mClipBounds
.TopLeft(), init
);
663 mSharedContext
->ClearLastTexture();
664 // Bind the new clip mask to the clip sampler on texture unit 1.
665 mSharedContext
->SetClipMask(mClipMask
);
666 mSharedContext
->SetClipRect(mClipBounds
);
667 // We uploaded a surface, just as if we missed the texture cache, so account
669 mProfile
.OnCacheMiss();
673 bool DrawTargetWebgl::SetSimpleClipRect() {
674 // Determine whether the clipping rectangle is simple enough to accelerate.
675 // Check if there is a device space clip rectangle available from the Skia
677 if (Maybe
<IntRect
> clip
= mSkia
->GetDeviceClipRect(false)) {
678 // If the clip is empty, leave the final integer clip rectangle empty to
679 // trivially discard the draw request.
680 // If the clip rect is larger than the viewport, just set it to the
682 if (!clip
->IsEmpty() && clip
->Contains(GetRect())) {
683 clip
= Some(GetRect());
685 mSharedContext
->SetClipRect(*clip
);
686 mSharedContext
->SetNoClipMask();
690 // There was no pixel-aligned clip rect available, so check the clip stack to
691 // see if there is an AA'd axis-aligned rectangle clip.
692 Rect
rect(GetRect());
693 for (auto& clipStack
: mClipStack
) {
694 // If clip is a path or it has a non-axis-aligned transform, then it is
696 if (clipStack
.mPath
||
697 !clipStack
.mTransform
.PreservesAxisAlignedRectangles()) {
700 // Transform the rect and intersect it with the current clip.
702 clipStack
.mTransform
.TransformBounds(clipStack
.mRect
).Intersect(rect
);
704 mSharedContext
->SetClipRect(rect
);
705 mSharedContext
->SetNoClipMask();
709 // Installs the Skia clip rectangle, if applicable, onto the shared WebGL
710 // context as well as sets the WebGL framebuffer to the current target.
711 bool DrawTargetWebgl::PrepareContext(bool aClipped
) {
713 // If no clipping requested, just set the clip rect to the viewport.
714 mSharedContext
->SetClipRect(GetRect());
715 mSharedContext
->SetNoClipMask();
716 // Ensure the clip gets reset if clipping is later requested for the target.
717 mRefreshClipState
= true;
718 } else if (mRefreshClipState
|| !mSharedContext
->IsCurrentTarget(this)) {
719 // Try to use a simple clip rect if possible. Otherwise, fall back to
720 // generating a clip mask texture that can represent complex clip regions.
721 if (!SetSimpleClipRect() && !GenerateComplexClipMask()) {
724 mClipChanged
= false;
725 mRefreshClipState
= false;
727 return mSharedContext
->SetTarget(this);
730 bool SharedContextWebgl::IsContextLost() const {
731 return !mWebgl
|| mWebgl
->IsContextLost();
734 // Signal to CanvasRenderingContext2D when the WebGL context is lost.
735 bool DrawTargetWebgl::IsValid() const {
736 return mSharedContext
&& !mSharedContext
->IsContextLost();
739 bool DrawTargetWebgl::CanCreate(const IntSize
& aSize
, SurfaceFormat aFormat
) {
740 if (!gfxVars::UseAcceleratedCanvas2D()) {
744 if (!Factory::AllowedSurfaceSize(aSize
)) {
748 // The interpretation of the min-size and max-size follows from the old
749 // SkiaGL prefs. First just ensure that the context is not unreasonably
751 static const int32_t kMinDimension
= 16;
752 if (std::min(aSize
.width
, aSize
.height
) < kMinDimension
) {
756 int32_t minSize
= StaticPrefs::gfx_canvas_accelerated_min_size();
757 if (aSize
.width
* aSize
.height
< minSize
* minSize
) {
761 // Maximum pref allows 3 different options:
762 // 0 means unlimited size,
763 // > 0 means use value as an absolute threshold,
764 // < 0 means use the number of screen pixels as a threshold.
765 int32_t maxSize
= StaticPrefs::gfx_canvas_accelerated_max_size();
767 if (std::max(aSize
.width
, aSize
.height
) > maxSize
) {
770 } else if (maxSize
< 0) {
771 // Default to historical mobile screen size of 980x480, like FishIEtank.
772 // In addition, allow acceleration up to this size even if the screen is
773 // smaller. A lot content expects this size to work well. See Bug 999841
774 static const int32_t kScreenPixels
= 980 * 480;
776 if (RefPtr
<widget::Screen
> screen
=
777 widget::ScreenManager::GetSingleton().GetPrimaryScreen()) {
778 LayoutDeviceIntSize screenSize
= screen
->GetRect().Size();
779 if (aSize
.width
* aSize
.height
>
780 std::max(screenSize
.width
* screenSize
.height
, kScreenPixels
)) {
789 already_AddRefed
<DrawTargetWebgl
> DrawTargetWebgl::Create(
790 const IntSize
& aSize
, SurfaceFormat aFormat
,
791 const RefPtr
<SharedContextWebgl
>& aSharedContext
) {
792 // Validate the size and format.
793 if (!CanCreate(aSize
, aFormat
)) {
797 RefPtr
<DrawTargetWebgl
> dt
= new DrawTargetWebgl
;
798 if (!dt
->Init(aSize
, aFormat
, aSharedContext
) || !dt
->IsValid()) {
805 void* DrawTargetWebgl::GetNativeSurface(NativeSurfaceType aType
) {
807 case NativeSurfaceType::WEBGL_CONTEXT
:
808 // If the context is lost, then don't attempt to access it.
809 if (mSharedContext
->IsContextLost()) {
815 return mSharedContext
->mWebgl
.get();
821 // Wrap a WebGL texture holding a snapshot with a texture handle. Note that
822 // while the texture is still in use as the backing texture of a framebuffer,
823 // it's texture memory is not currently tracked with other texture handles.
824 // Once it is finally orphaned and used as a texture handle, it must be added
825 // to the resource usage totals.
826 already_AddRefed
<TextureHandle
> SharedContextWebgl::WrapSnapshot(
827 const IntSize
& aSize
, SurfaceFormat aFormat
, RefPtr
<WebGLTexture
> aTex
) {
828 // Ensure there is enough space for the texture.
829 size_t usedBytes
= BackingTexture::UsedBytes(aFormat
, aSize
);
830 PruneTextureMemory(usedBytes
, false);
831 // Allocate a handle for the texture
832 RefPtr
<StandaloneTexture
> handle
=
833 new StandaloneTexture(aSize
, aFormat
, aTex
.forget());
834 mStandaloneTextures
.push_back(handle
);
835 mTextureHandles
.insertFront(handle
);
836 mTotalTextureMemory
+= usedBytes
;
837 mUsedTextureMemory
+= usedBytes
;
838 ++mNumTextureHandles
;
839 return handle
.forget();
842 void SharedContextWebgl::SetTexFilter(WebGLTexture
* aTex
, bool aFilter
) {
843 mWebgl
->TexParameter_base(
844 LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_MAG_FILTER
,
845 FloatOrInt(aFilter
? LOCAL_GL_LINEAR
: LOCAL_GL_NEAREST
));
846 mWebgl
->TexParameter_base(
847 LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_MIN_FILTER
,
848 FloatOrInt(aFilter
? LOCAL_GL_LINEAR
: LOCAL_GL_NEAREST
));
851 void SharedContextWebgl::InitTexParameters(WebGLTexture
* aTex
, bool aFilter
) {
852 mWebgl
->TexParameter_base(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_WRAP_S
,
853 FloatOrInt(LOCAL_GL_REPEAT
));
854 mWebgl
->TexParameter_base(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_WRAP_T
,
855 FloatOrInt(LOCAL_GL_REPEAT
));
856 SetTexFilter(aTex
, aFilter
);
859 // Copy the contents of the WebGL framebuffer into a WebGL texture.
860 already_AddRefed
<TextureHandle
> SharedContextWebgl::CopySnapshot(
861 const IntRect
& aRect
, TextureHandle
* aHandle
) {
862 if (!mWebgl
|| mWebgl
->IsContextLost()) {
866 // If the target is going away, then we can just directly reuse the
867 // framebuffer texture since it will never change.
868 RefPtr
<WebGLTexture
> tex
= mWebgl
->CreateTexture();
873 // If copying from a non-DT source, we have to bind a scratch framebuffer for
876 if (!mScratchFramebuffer
) {
877 mScratchFramebuffer
= mWebgl
->CreateFramebuffer();
879 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mScratchFramebuffer
);
881 webgl::FbAttachInfo attachInfo
;
882 attachInfo
.tex
= aHandle
->GetBackingTexture()->GetWebGLTexture();
883 mWebgl
->FramebufferAttach(LOCAL_GL_FRAMEBUFFER
, LOCAL_GL_COLOR_ATTACHMENT0
,
884 LOCAL_GL_TEXTURE_2D
, attachInfo
);
887 // Create a texture to hold the copy
888 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, tex
);
889 mWebgl
->TexStorage(LOCAL_GL_TEXTURE_2D
, 1, LOCAL_GL_RGBA8
,
890 {uint32_t(aRect
.width
), uint32_t(aRect
.height
), 1});
891 InitTexParameters(tex
);
892 // Copy the framebuffer into the texture
893 mWebgl
->CopyTexImage(LOCAL_GL_TEXTURE_2D
, 0, 0, {0, 0, 0}, {aRect
.x
, aRect
.y
},
894 {uint32_t(aRect
.width
), uint32_t(aRect
.height
)});
897 SurfaceFormat format
=
898 aHandle
? aHandle
->GetFormat() : mCurrentTarget
->GetFormat();
899 already_AddRefed
<TextureHandle
> result
=
900 WrapSnapshot(aRect
.Size(), format
, tex
.forget());
902 // Restore the actual framebuffer after reading is done.
903 if (aHandle
&& mCurrentTarget
) {
904 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mCurrentTarget
->mFramebuffer
);
910 inline DrawTargetWebgl::AutoRestoreContext::AutoRestoreContext(
911 DrawTargetWebgl
* aTarget
)
913 mClipAARect(aTarget
->mSharedContext
->mClipAARect
),
914 mLastClipMask(aTarget
->mSharedContext
->mLastClipMask
) {}
916 inline DrawTargetWebgl::AutoRestoreContext::~AutoRestoreContext() {
917 mTarget
->mSharedContext
->SetClipRect(mClipAARect
);
919 mTarget
->mSharedContext
->SetClipMask(mLastClipMask
);
921 mTarget
->mRefreshClipState
= true;
924 // Utility method to install the target before copying a snapshot.
925 already_AddRefed
<TextureHandle
> DrawTargetWebgl::CopySnapshot(
926 const IntRect
& aRect
) {
927 AutoRestoreContext
restore(this);
928 if (!PrepareContext(false)) {
931 return mSharedContext
->CopySnapshot(aRect
);
934 bool DrawTargetWebgl::HasDataSnapshot() const {
935 return (mSkiaValid
&& !mSkiaLayer
) || (mSnapshot
&& mSnapshot
->HasReadData());
938 bool DrawTargetWebgl::PrepareSkia() {
941 } else if (mSkiaLayer
) {
947 bool DrawTargetWebgl::EnsureDataSnapshot() {
948 return HasDataSnapshot() || PrepareSkia();
951 void DrawTargetWebgl::PrepareShmem() { PrepareSkia(); }
953 // Borrow a snapshot that may be used by another thread for composition. Only
954 // Skia snapshots are safe to pass around.
955 already_AddRefed
<SourceSurface
> DrawTargetWebgl::GetDataSnapshot() {
957 return mSkia
->Snapshot(mFormat
);
960 already_AddRefed
<SourceSurface
> DrawTargetWebgl::Snapshot() {
961 // If already using the Skia fallback, then just snapshot that.
963 return GetDataSnapshot();
966 // There's no valid Skia snapshot, so we need to get one from the WebGL
969 // Create a copy-on-write reference to this target.
970 mSnapshot
= new SourceSurfaceWebgl(this);
972 return do_AddRef(mSnapshot
);
975 // If we need to provide a snapshot for another DrawTargetWebgl that shares the
976 // same WebGL context, then it is safe to directly return a snapshot. Otherwise,
977 // we may be exporting to another thread and require a data snapshot.
978 already_AddRefed
<SourceSurface
> DrawTargetWebgl::GetOptimizedSnapshot(
979 DrawTarget
* aTarget
) {
980 if (aTarget
&& aTarget
->GetBackendType() == BackendType::WEBGL
&&
981 static_cast<DrawTargetWebgl
*>(aTarget
)->mSharedContext
==
985 return GetDataSnapshot();
988 // Read from the WebGL context into a buffer. This handles both swizzling BGRA
989 // to RGBA and flipping the image.
990 bool SharedContextWebgl::ReadInto(uint8_t* aDstData
, int32_t aDstStride
,
991 SurfaceFormat aFormat
, const IntRect
& aBounds
,
992 TextureHandle
* aHandle
) {
993 MOZ_ASSERT(aFormat
== SurfaceFormat::B8G8R8A8
||
994 aFormat
== SurfaceFormat::B8G8R8X8
);
996 // If reading into a new texture, we have to bind it to a scratch framebuffer
999 if (!mScratchFramebuffer
) {
1000 mScratchFramebuffer
= mWebgl
->CreateFramebuffer();
1002 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mScratchFramebuffer
);
1003 webgl::FbAttachInfo attachInfo
;
1004 attachInfo
.tex
= aHandle
->GetBackingTexture()->GetWebGLTexture();
1005 mWebgl
->FramebufferAttach(LOCAL_GL_FRAMEBUFFER
, LOCAL_GL_COLOR_ATTACHMENT0
,
1006 LOCAL_GL_TEXTURE_2D
, attachInfo
);
1007 } else if (mCurrentTarget
&& mCurrentTarget
->mIsClear
) {
1008 // If reading from a target that is still clear, then avoid the readback by
1009 // just clearing the data.
1010 SkPixmap(MakeSkiaImageInfo(aBounds
.Size(), aFormat
), aDstData
, aDstStride
)
1011 .erase(IsOpaque(aFormat
) ? SK_ColorBLACK
: SK_ColorTRANSPARENT
);
1015 webgl::ReadPixelsDesc desc
;
1016 desc
.srcOffset
= *ivec2::From(aBounds
);
1017 desc
.size
= *uvec2::FromSize(aBounds
);
1018 desc
.packState
.rowLength
= aDstStride
/ 4;
1019 Range
<uint8_t> range
= {aDstData
, size_t(aDstStride
) * aBounds
.height
};
1020 mWebgl
->ReadPixelsInto(desc
, range
);
1022 // Restore the actual framebuffer after reading is done.
1023 if (aHandle
&& mCurrentTarget
) {
1024 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mCurrentTarget
->mFramebuffer
);
1030 already_AddRefed
<DataSourceSurface
> SharedContextWebgl::ReadSnapshot(
1031 TextureHandle
* aHandle
) {
1032 // Allocate a data surface, map it, and read from the WebGL context into the
1034 SurfaceFormat format
= SurfaceFormat::UNKNOWN
;
1037 format
= aHandle
->GetFormat();
1038 bounds
= aHandle
->GetBounds();
1040 format
= mCurrentTarget
->GetFormat();
1041 bounds
= mCurrentTarget
->GetRect();
1043 RefPtr
<DataSourceSurface
> surface
=
1044 Factory::CreateDataSourceSurface(bounds
.Size(), format
);
1048 DataSourceSurface::ScopedMap
dstMap(surface
, DataSourceSurface::WRITE
);
1049 if (!dstMap
.IsMapped() || !ReadInto(dstMap
.GetData(), dstMap
.GetStride(),
1050 format
, bounds
, aHandle
)) {
1053 return surface
.forget();
1056 // Utility method to install the target before reading a snapshot.
1057 bool DrawTargetWebgl::ReadInto(uint8_t* aDstData
, int32_t aDstStride
) {
1058 if (!PrepareContext(false)) {
1062 return mSharedContext
->ReadInto(aDstData
, aDstStride
, GetFormat(), GetRect());
1065 // Utility method to install the target before reading a snapshot.
1066 already_AddRefed
<DataSourceSurface
> DrawTargetWebgl::ReadSnapshot() {
1067 AutoRestoreContext
restore(this);
1068 if (!PrepareContext(false)) {
1071 mProfile
.OnReadback();
1072 return mSharedContext
->ReadSnapshot();
1075 already_AddRefed
<SourceSurface
> DrawTargetWebgl::GetBackingSurface() {
1079 void DrawTargetWebgl::DetachAllSnapshots() {
1080 mSkia
->DetachAllSnapshots();
1084 // Prepare the framebuffer for accelerated drawing. Any cached snapshots will
1085 // be invalidated if not detached and copied here. Ensure the WebGL
1086 // framebuffer's contents are updated if still somehow stored in the Skia
1088 bool DrawTargetWebgl::MarkChanged() {
1090 // Try to copy the target into a new texture if possible.
1091 ClearSnapshot(true, true);
1093 if (!mWebglValid
&& !FlushFromSkia()) {
1101 void DrawTargetWebgl::MarkSkiaChanged(bool aOverwrite
) {
1105 } else if (!mSkiaValid
) {
1106 if (ReadIntoSkia()) {
1107 // Signal that we've hit a complete software fallback.
1108 mProfile
.OnFallback();
1110 } else if (mSkiaLayer
) {
1113 mWebglValid
= false;
1117 // Whether a given composition operator is associative and thus allows drawing
1118 // into a separate layer that can be later composited back into the WebGL
1120 static inline bool SupportsLayering(const DrawOptions
& aOptions
) {
1121 switch (aOptions
.mCompositionOp
) {
1122 case CompositionOp::OP_OVER
:
1123 // Layering is only supported for the default source-over composition op.
1130 void DrawTargetWebgl::MarkSkiaChanged(const DrawOptions
& aOptions
) {
1131 if (SupportsLayering(aOptions
)) {
1133 // If the Skia context needs initialization, clear it and enable layering.
1138 mSkiaLayerClear
= mIsClear
;
1139 mSkia
->DetachAllSnapshots();
1140 if (mSkiaLayerClear
) {
1141 // Avoid blending later by making sure the layer background is filled
1142 // with opaque alpha values if necessary.
1143 mSkiaNoClip
->FillRect(Rect(mSkiaNoClip
->GetRect()), GetClearPattern(),
1144 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
1146 mSkiaNoClip
->ClearRect(Rect(mSkiaNoClip
->GetRect()));
1150 // The WebGL context is no longer up-to-date.
1151 mWebglValid
= false;
1154 // For other composition ops, just overwrite the Skia data.
1159 bool DrawTargetWebgl::LockBits(uint8_t** aData
, IntSize
* aSize
,
1160 int32_t* aStride
, SurfaceFormat
* aFormat
,
1161 IntPoint
* aOrigin
) {
1162 // Can only access pixels if there is valid, flattened Skia data.
1163 if (mSkiaValid
&& !mSkiaLayer
) {
1165 return mSkia
->LockBits(aData
, aSize
, aStride
, aFormat
, aOrigin
);
1170 void DrawTargetWebgl::ReleaseBits(uint8_t* aData
) {
1171 // Can only access pixels if there is valid, flattened Skia data.
1172 if (mSkiaValid
&& !mSkiaLayer
) {
1173 mSkia
->ReleaseBits(aData
);
1177 // Format is x, y, alpha
1178 static const float kRectVertexData
[12] = {0.0f
, 0.0f
, 1.0f
, 1.0f
, 0.0f
, 1.0f
,
1179 1.0f
, 1.0f
, 1.0f
, 0.0f
, 1.0f
, 1.0f
};
1181 // Orphans the contents of the path vertex buffer. The beginning of the buffer
1182 // always contains data for a simple rectangle draw to avoid needing to switch
1184 void SharedContextWebgl::ResetPathVertexBuffer(bool aChanged
) {
1185 mWebgl
->BindBuffer(LOCAL_GL_ARRAY_BUFFER
, mPathVertexBuffer
.get());
1186 mWebgl
->UninitializedBufferData_SizeOnly(
1187 LOCAL_GL_ARRAY_BUFFER
,
1188 std::max(size_t(mPathVertexCapacity
), sizeof(kRectVertexData
)),
1189 LOCAL_GL_DYNAMIC_DRAW
);
1190 mWebgl
->BufferSubData(LOCAL_GL_ARRAY_BUFFER
, 0, sizeof(kRectVertexData
),
1191 (const uint8_t*)kRectVertexData
);
1192 mPathVertexOffset
= sizeof(kRectVertexData
);
1194 mWGROutputBuffer
.reset(
1195 mPathVertexCapacity
> 0
1196 ? new (fallible
) WGR::OutputVertex
[mPathVertexCapacity
/
1197 sizeof(WGR::OutputVertex
)]
1202 // Attempts to create all shaders and resources to be used for drawing commands.
1203 // Returns whether or not this succeeded.
1204 bool SharedContextWebgl::CreateShaders() {
1205 if (!mPathVertexArray
) {
1206 mPathVertexArray
= mWebgl
->CreateVertexArray();
1208 if (!mPathVertexBuffer
) {
1209 mPathVertexBuffer
= mWebgl
->CreateBuffer();
1210 mWebgl
->BindVertexArray(mPathVertexArray
.get());
1211 ResetPathVertexBuffer();
1212 mWebgl
->EnableVertexAttribArray(0);
1214 webgl::VertAttribPointerDesc attribDesc
;
1215 attribDesc
.channels
= 3;
1216 attribDesc
.type
= LOCAL_GL_FLOAT
;
1217 attribDesc
.normalized
= false;
1218 mWebgl
->VertexAttribPointer(0, attribDesc
);
1220 if (!mSolidProgram
) {
1221 // AA is computed by using the basis vectors of the transform to determine
1222 // both the scale and orientation. The scale is then used to extrude the
1223 // rectangle outward by 1 screen-space pixel to account for the AA region.
1224 // The distance to the rectangle edges is passed to the fragment shader in
1225 // an interpolant, biased by 0.5 so it represents the desired coverage. The
1226 // minimum coverage is then chosen by the fragment shader to use as an AA
1227 // coverage value to modulate the color.
1229 "attribute vec3 a_vertex;\n"
1230 "uniform vec2 u_transform[3];\n"
1231 "uniform vec2 u_viewport;\n"
1232 "uniform vec4 u_clipbounds;\n"
1233 "uniform float u_aa;\n"
1234 "varying vec2 v_cliptc;\n"
1235 "varying vec4 v_clipdist;\n"
1236 "varying vec4 v_dist;\n"
1237 "varying float v_alpha;\n"
1239 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1240 " dot(u_transform[1], u_transform[1]));\n"
1241 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1242 " scale *= invScale;\n"
1243 " vec2 extrude = a_vertex.xy +\n"
1244 " invScale * (2.0 * a_vertex.xy - 1.0);\n"
1245 " vec2 vertex = u_transform[0] * extrude.x +\n"
1246 " u_transform[1] * extrude.y +\n"
1247 " u_transform[2];\n"
1248 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1249 " v_cliptc = vertex / u_viewport;\n"
1250 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
1251 " u_clipbounds.zw - vertex);\n"
1252 " float noAA = 1.0 - u_aa;\n"
1253 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 0.5 + noAA;\n"
1254 " v_alpha = min(a_vertex.z,\n"
1255 " min(scale.x, 1.0) * min(scale.y, 1.0) + noAA);\n"
1258 "precision mediump float;\n"
1259 "uniform vec4 u_color;\n"
1260 "uniform sampler2D u_clipmask;\n"
1261 "varying highp vec2 v_cliptc;\n"
1262 "varying vec4 v_clipdist;\n"
1263 "varying vec4 v_dist;\n"
1264 "varying float v_alpha;\n"
1266 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1267 " vec4 dist = min(v_dist, v_clipdist);\n"
1268 " dist.xy = min(dist.xy, dist.zw);\n"
1269 " float aa = clamp(min(dist.x, dist.y), 0.0, v_alpha);\n"
1270 " gl_FragColor = clip * aa * u_color;\n"
1272 RefPtr
<WebGLShader
> vsId
= mWebgl
->CreateShader(LOCAL_GL_VERTEX_SHADER
);
1273 mWebgl
->ShaderSource(*vsId
, vsSource
);
1274 mWebgl
->CompileShader(*vsId
);
1275 if (!mWebgl
->GetCompileResult(*vsId
).success
) {
1278 RefPtr
<WebGLShader
> fsId
= mWebgl
->CreateShader(LOCAL_GL_FRAGMENT_SHADER
);
1279 mWebgl
->ShaderSource(*fsId
, fsSource
);
1280 mWebgl
->CompileShader(*fsId
);
1281 if (!mWebgl
->GetCompileResult(*fsId
).success
) {
1284 mSolidProgram
= mWebgl
->CreateProgram();
1285 mWebgl
->AttachShader(*mSolidProgram
, *vsId
);
1286 mWebgl
->AttachShader(*mSolidProgram
, *fsId
);
1287 mWebgl
->BindAttribLocation(*mSolidProgram
, 0, "a_vertex");
1288 mWebgl
->LinkProgram(*mSolidProgram
);
1289 if (!mWebgl
->GetLinkResult(*mSolidProgram
).success
) {
1292 mSolidProgramViewport
= GetUniformLocation(mSolidProgram
, "u_viewport");
1293 mSolidProgramAA
= GetUniformLocation(mSolidProgram
, "u_aa");
1294 mSolidProgramTransform
= GetUniformLocation(mSolidProgram
, "u_transform");
1295 mSolidProgramColor
= GetUniformLocation(mSolidProgram
, "u_color");
1296 mSolidProgramClipMask
= GetUniformLocation(mSolidProgram
, "u_clipmask");
1297 mSolidProgramClipBounds
= GetUniformLocation(mSolidProgram
, "u_clipbounds");
1298 if (!mSolidProgramViewport
|| !mSolidProgramAA
|| !mSolidProgramTransform
||
1299 !mSolidProgramColor
|| !mSolidProgramClipMask
||
1300 !mSolidProgramClipBounds
) {
1303 mWebgl
->UseProgram(mSolidProgram
);
1304 UniformData(LOCAL_GL_INT
, mSolidProgramClipMask
, Array
<int32_t, 1>{1});
1307 if (!mImageProgram
) {
1309 "attribute vec3 a_vertex;\n"
1310 "uniform vec2 u_viewport;\n"
1311 "uniform vec4 u_clipbounds;\n"
1312 "uniform float u_aa;\n"
1313 "uniform vec2 u_transform[3];\n"
1314 "uniform vec2 u_texmatrix[3];\n"
1315 "varying vec2 v_cliptc;\n"
1316 "varying vec2 v_texcoord;\n"
1317 "varying vec4 v_clipdist;\n"
1318 "varying vec4 v_dist;\n"
1319 "varying float v_alpha;\n"
1321 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1322 " dot(u_transform[1], u_transform[1]));\n"
1323 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1324 " scale *= invScale;\n"
1325 " vec2 extrude = a_vertex.xy +\n"
1326 " invScale * (2.0 * a_vertex.xy - 1.0);\n"
1327 " vec2 vertex = u_transform[0] * extrude.x +\n"
1328 " u_transform[1] * extrude.y +\n"
1329 " u_transform[2];\n"
1330 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1331 " v_cliptc = vertex / u_viewport;\n"
1332 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
1333 " u_clipbounds.zw - vertex);\n"
1334 " v_texcoord = u_texmatrix[0] * extrude.x +\n"
1335 " u_texmatrix[1] * extrude.y +\n"
1336 " u_texmatrix[2];\n"
1337 " float noAA = 1.0 - u_aa;\n"
1338 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 0.5 + noAA;\n"
1339 " v_alpha = min(a_vertex.z,\n"
1340 " min(scale.x, 1.0) * min(scale.y, 1.0) + noAA);\n"
1343 "precision mediump float;\n"
1344 "uniform vec4 u_texbounds;\n"
1345 "uniform vec4 u_color;\n"
1346 "uniform float u_swizzle;\n"
1347 "uniform sampler2D u_sampler;\n"
1348 "uniform sampler2D u_clipmask;\n"
1349 "varying highp vec2 v_cliptc;\n"
1350 "varying highp vec2 v_texcoord;\n"
1351 "varying vec4 v_clipdist;\n"
1352 "varying vec4 v_dist;\n"
1353 "varying float v_alpha;\n"
1355 " highp vec2 tc = clamp(v_texcoord, u_texbounds.xy,\n"
1356 " u_texbounds.zw);\n"
1357 " vec4 image = texture2D(u_sampler, tc);\n"
1358 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1359 " vec4 dist = min(v_dist, v_clipdist);\n"
1360 " dist.xy = min(dist.xy, dist.zw);\n"
1361 " float aa = clamp(min(dist.x, dist.y), 0.0, v_alpha);\n"
1362 " gl_FragColor = clip * aa * u_color *\n"
1363 " mix(image, image.rrrr, u_swizzle);\n"
1365 RefPtr
<WebGLShader
> vsId
= mWebgl
->CreateShader(LOCAL_GL_VERTEX_SHADER
);
1366 mWebgl
->ShaderSource(*vsId
, vsSource
);
1367 mWebgl
->CompileShader(*vsId
);
1368 if (!mWebgl
->GetCompileResult(*vsId
).success
) {
1371 RefPtr
<WebGLShader
> fsId
= mWebgl
->CreateShader(LOCAL_GL_FRAGMENT_SHADER
);
1372 mWebgl
->ShaderSource(*fsId
, fsSource
);
1373 mWebgl
->CompileShader(*fsId
);
1374 if (!mWebgl
->GetCompileResult(*fsId
).success
) {
1377 mImageProgram
= mWebgl
->CreateProgram();
1378 mWebgl
->AttachShader(*mImageProgram
, *vsId
);
1379 mWebgl
->AttachShader(*mImageProgram
, *fsId
);
1380 mWebgl
->BindAttribLocation(*mImageProgram
, 0, "a_vertex");
1381 mWebgl
->LinkProgram(*mImageProgram
);
1382 if (!mWebgl
->GetLinkResult(*mImageProgram
).success
) {
1385 mImageProgramViewport
= GetUniformLocation(mImageProgram
, "u_viewport");
1386 mImageProgramAA
= GetUniformLocation(mImageProgram
, "u_aa");
1387 mImageProgramTransform
= GetUniformLocation(mImageProgram
, "u_transform");
1388 mImageProgramTexMatrix
= GetUniformLocation(mImageProgram
, "u_texmatrix");
1389 mImageProgramTexBounds
= GetUniformLocation(mImageProgram
, "u_texbounds");
1390 mImageProgramSwizzle
= GetUniformLocation(mImageProgram
, "u_swizzle");
1391 mImageProgramColor
= GetUniformLocation(mImageProgram
, "u_color");
1392 mImageProgramSampler
= GetUniformLocation(mImageProgram
, "u_sampler");
1393 mImageProgramClipMask
= GetUniformLocation(mImageProgram
, "u_clipmask");
1394 mImageProgramClipBounds
= GetUniformLocation(mImageProgram
, "u_clipbounds");
1395 if (!mImageProgramViewport
|| !mImageProgramAA
|| !mImageProgramTransform
||
1396 !mImageProgramTexMatrix
|| !mImageProgramTexBounds
||
1397 !mImageProgramSwizzle
|| !mImageProgramColor
|| !mImageProgramSampler
||
1398 !mImageProgramClipMask
|| !mImageProgramClipBounds
) {
1401 mWebgl
->UseProgram(mImageProgram
);
1402 UniformData(LOCAL_GL_INT
, mImageProgramSampler
, Array
<int32_t, 1>{0});
1403 UniformData(LOCAL_GL_INT
, mImageProgramClipMask
, Array
<int32_t, 1>{1});
1408 void SharedContextWebgl::EnableScissor(const IntRect
& aRect
) {
1409 // Only update scissor state if it actually changes.
1410 if (!mLastScissor
.IsEqualEdges(aRect
)) {
1411 mLastScissor
= aRect
;
1412 mWebgl
->Scissor(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
);
1414 if (!mScissorEnabled
) {
1415 mScissorEnabled
= true;
1416 mWebgl
->SetEnabled(LOCAL_GL_SCISSOR_TEST
, {}, true);
1420 void SharedContextWebgl::DisableScissor() {
1421 if (mScissorEnabled
) {
1422 mScissorEnabled
= false;
1423 mWebgl
->SetEnabled(LOCAL_GL_SCISSOR_TEST
, {}, false);
1427 inline ColorPattern
DrawTargetWebgl::GetClearPattern() const {
1428 return ColorPattern(
1429 DeviceColor(0.0f
, 0.0f
, 0.0f
, IsOpaque(mFormat
) ? 1.0f
: 0.0f
));
1432 template <typename R
>
1433 inline RectDouble
DrawTargetWebgl::TransformDouble(const R
& aRect
) const {
1434 return MatrixDouble(mTransform
).TransformBounds(WidenToDouble(aRect
));
1437 // Check if the transformed rect clips to the viewport.
1438 inline Maybe
<Rect
> DrawTargetWebgl::RectClippedToViewport(
1439 const RectDouble
& aRect
) const {
1440 if (!mTransform
.PreservesAxisAlignedRectangles()) {
1444 return Some(NarrowToFloat(aRect
.SafeIntersect(RectDouble(GetRect()))));
1447 // Ensure that the rect, after transform, is within reasonable precision limits
1448 // such that when transformed and clipped in the shader it will not round bits
1449 // from the mantissa in a way that will diverge in a noticeable way from path
1450 // geometry calculated by the path fallback.
1451 template <typename R
>
1452 static inline bool RectInsidePrecisionLimits(const R
& aRect
) {
1453 return R(-(1 << 20), -(1 << 20), 2 << 20, 2 << 20).Contains(aRect
);
1456 void DrawTargetWebgl::ClearRect(const Rect
& aRect
) {
1458 // No need to clear anything if the entire framebuffer is already clear.
1462 RectDouble xformRect
= TransformDouble(aRect
);
1463 bool containsViewport
= false;
1464 if (Maybe
<Rect
> clipped
= RectClippedToViewport(xformRect
)) {
1465 // If the rect clips to viewport, just clear the clipped rect
1466 // to avoid transform issues.
1467 containsViewport
= clipped
->Size() == Size(GetSize());
1468 DrawRect(*clipped
, GetClearPattern(),
1469 DrawOptions(1.0f
, CompositionOp::OP_CLEAR
), Nothing(), nullptr,
1471 } else if (RectInsidePrecisionLimits(xformRect
)) {
1472 // If the rect transform won't stress precision, then just use it.
1473 DrawRect(aRect
, GetClearPattern(),
1474 DrawOptions(1.0f
, CompositionOp::OP_CLEAR
));
1476 // Otherwise, using the transform in the shader may lead to inaccuracies, so
1479 mSkia
->ClearRect(aRect
);
1482 // If the clear rectangle encompasses the entire viewport and is not clipped,
1483 // then mark the target as entirely clear.
1484 if (containsViewport
&& mSharedContext
->IsCurrentTarget(this) &&
1485 !mSharedContext
->HasClipMask() &&
1486 mSharedContext
->mClipAARect
.Contains(Rect(GetRect()))) {
1491 static inline DeviceColor
PremultiplyColor(const DeviceColor
& aColor
,
1492 float aAlpha
= 1.0f
) {
1493 float a
= aColor
.a
* aAlpha
;
1494 return DeviceColor(aColor
.r
* a
, aColor
.g
* a
, aColor
.b
* a
, a
);
1497 // Attempts to create the framebuffer used for drawing and also any relevant
1498 // non-shared resources. Returns whether or not this succeeded.
1499 bool DrawTargetWebgl::CreateFramebuffer() {
1500 RefPtr
<WebGLContext
> webgl
= mSharedContext
->mWebgl
;
1501 if (!mFramebuffer
) {
1502 mFramebuffer
= webgl
->CreateFramebuffer();
1505 mTex
= webgl
->CreateTexture();
1506 webgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mTex
);
1507 webgl
->TexStorage(LOCAL_GL_TEXTURE_2D
, 1, LOCAL_GL_RGBA8
,
1508 {uint32_t(mSize
.width
), uint32_t(mSize
.height
), 1});
1509 mSharedContext
->InitTexParameters(mTex
);
1510 webgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mFramebuffer
);
1511 webgl::FbAttachInfo attachInfo
;
1512 attachInfo
.tex
= mTex
;
1513 webgl
->FramebufferAttach(LOCAL_GL_FRAMEBUFFER
, LOCAL_GL_COLOR_ATTACHMENT0
,
1514 LOCAL_GL_TEXTURE_2D
, attachInfo
);
1515 webgl
->Viewport(0, 0, mSize
.width
, mSize
.height
);
1516 mSharedContext
->DisableScissor();
1517 DeviceColor color
= PremultiplyColor(GetClearPattern().mColor
);
1518 webgl
->ClearColor(color
.b
, color
.g
, color
.r
, color
.a
);
1519 webgl
->Clear(LOCAL_GL_COLOR_BUFFER_BIT
);
1520 mSharedContext
->ClearTarget();
1521 mSharedContext
->ClearLastTexture();
1526 void DrawTargetWebgl::CopySurface(SourceSurface
* aSurface
,
1527 const IntRect
& aSourceRect
,
1528 const IntPoint
& aDestination
) {
1529 // Intersect the source and destination rectangles with the viewport bounds.
1531 IntRect(aDestination
, aSourceRect
.Size()).SafeIntersect(GetRect());
1532 IntRect srcRect
= destRect
- aDestination
+ aSourceRect
.TopLeft();
1533 if (srcRect
.IsEmpty()) {
1539 if (destRect
.Contains(GetRect())) {
1540 // If the the destination would override the entire layer, discard the
1543 } else if (!IsOpaque(aSurface
->GetFormat())) {
1544 // If the surface is not opaque, copying it into the layer results in
1545 // unintended blending rather than a copy to the destination.
1549 // If there is no layer, copying is safe.
1552 mSkia
->CopySurface(aSurface
, srcRect
, destRect
.TopLeft());
1556 IntRect samplingRect
;
1557 if (!mSharedContext
->IsCompatibleSurface(aSurface
)) {
1558 // If this data surface completely overwrites the framebuffer, then just
1559 // copy it to the Skia target.
1560 if (destRect
.Contains(GetRect())) {
1561 MarkSkiaChanged(true);
1562 mSkia
->DetachAllSnapshots();
1563 mSkiaNoClip
->CopySurface(aSurface
, srcRect
, destRect
.TopLeft());
1567 // CopySurface usually only samples a surface once, so don't cache the
1568 // entire surface as it is unlikely to be reused. Limit it to the used
1569 // source rectangle instead.
1570 IntRect surfaceRect
= aSurface
->GetRect();
1571 if (!srcRect
.IsEqualEdges(surfaceRect
)) {
1572 samplingRect
= srcRect
.SafeIntersect(surfaceRect
);
1576 Matrix matrix
= Matrix::Translation(destRect
.TopLeft() - srcRect
.TopLeft());
1577 SurfacePattern
pattern(aSurface
, ExtendMode::CLAMP
, matrix
,
1578 SamplingFilter::POINT
, samplingRect
);
1579 DrawRect(Rect(destRect
), pattern
, DrawOptions(1.0f
, CompositionOp::OP_SOURCE
),
1580 Nothing(), nullptr, false, false);
1583 void DrawTargetWebgl::PushClip(const Path
* aPath
) {
1584 if (aPath
&& aPath
->GetBackendType() == BackendType::SKIA
) {
1585 // Detect if the path is really just a rect to simplify caching.
1586 if (Maybe
<Rect
> rect
= aPath
->AsRect()) {
1587 PushClipRect(*rect
);
1592 mClipChanged
= true;
1593 mRefreshClipState
= true;
1594 mSkia
->PushClip(aPath
);
1596 mClipStack
.push_back({GetTransform(), Rect(), aPath
});
1599 void DrawTargetWebgl::PushClipRect(const Rect
& aRect
) {
1600 mClipChanged
= true;
1601 mRefreshClipState
= true;
1602 mSkia
->PushClipRect(aRect
);
1604 mClipStack
.push_back({GetTransform(), aRect
, nullptr});
1607 void DrawTargetWebgl::PushDeviceSpaceClipRects(const IntRect
* aRects
,
1609 mClipChanged
= true;
1610 mRefreshClipState
= true;
1611 mSkia
->PushDeviceSpaceClipRects(aRects
, aCount
);
1613 for (uint32_t i
= 0; i
< aCount
; i
++) {
1614 mClipStack
.push_back({Matrix(), Rect(aRects
[i
]), nullptr});
1618 void DrawTargetWebgl::PopClip() {
1619 mClipChanged
= true;
1620 mRefreshClipState
= true;
1623 mClipStack
.pop_back();
1626 bool DrawTargetWebgl::RemoveAllClips() {
1627 if (mClipStack
.empty()) {
1630 if (!mSkia
->RemoveAllClips()) {
1633 mClipChanged
= true;
1634 mRefreshClipState
= true;
1639 bool DrawTargetWebgl::CopyToFallback(DrawTarget
* aDT
) {
1640 aDT
->RemoveAllClips();
1641 for (auto& clipStack
: mClipStack
) {
1642 aDT
->SetTransform(clipStack
.mTransform
);
1643 if (clipStack
.mPath
) {
1644 aDT
->PushClip(clipStack
.mPath
);
1646 aDT
->PushClipRect(clipStack
.mRect
);
1649 aDT
->SetTransform(GetTransform());
1651 // An existing data snapshot is required for fallback, as we have to avoid
1652 // trying to touch the WebGL context, which is assumed to be invalid and not
1653 // suitable for readback.
1654 if (HasDataSnapshot()) {
1655 if (RefPtr
<SourceSurface
> snapshot
= Snapshot()) {
1656 aDT
->CopySurface(snapshot
, snapshot
->GetRect(), gfx::IntPoint(0, 0));
1663 // Whether a given composition operator can be mapped to a WebGL blend mode.
1664 static inline bool SupportsDrawOptions(const DrawOptions
& aOptions
) {
1665 switch (aOptions
.mCompositionOp
) {
1666 case CompositionOp::OP_OVER
:
1667 case CompositionOp::OP_ADD
:
1668 case CompositionOp::OP_ATOP
:
1669 case CompositionOp::OP_SOURCE
:
1670 case CompositionOp::OP_CLEAR
:
1677 static inline bool SupportsExtendMode(const SurfacePattern
& aPattern
) {
1678 switch (aPattern
.mExtendMode
) {
1679 case ExtendMode::CLAMP
:
1681 case ExtendMode::REPEAT
:
1682 case ExtendMode::REPEAT_X
:
1683 case ExtendMode::REPEAT_Y
:
1684 if ((!aPattern
.mSurface
||
1685 aPattern
.mSurface
->GetType() == SurfaceType::WEBGL
) &&
1686 !aPattern
.mSamplingRect
.IsEmpty()) {
1695 // Whether a pattern can be mapped to an available WebGL shader.
1696 bool SharedContextWebgl::SupportsPattern(const Pattern
& aPattern
) {
1697 switch (aPattern
.GetType()) {
1698 case PatternType::COLOR
:
1700 case PatternType::SURFACE
: {
1701 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
1702 if (!SupportsExtendMode(surfacePattern
)) {
1705 if (surfacePattern
.mSurface
) {
1706 // If the surface is already uploaded to a texture, then just use it.
1707 if (IsCompatibleSurface(surfacePattern
.mSurface
)) {
1711 IntSize size
= surfacePattern
.mSurface
->GetSize();
1712 // The maximum size a surface can be before triggering a fallback to
1713 // software. Bound the maximum surface size by the actual texture size
1715 int32_t maxSize
= int32_t(
1716 std::min(StaticPrefs::gfx_canvas_accelerated_max_surface_size(),
1718 // Check if either of the surface dimensions or the sampling rect,
1719 // if supplied, exceed the maximum.
1720 if (std::max(size
.width
, size
.height
) > maxSize
&&
1721 (surfacePattern
.mSamplingRect
.IsEmpty() ||
1722 std::max(surfacePattern
.mSamplingRect
.width
,
1723 surfacePattern
.mSamplingRect
.height
) > maxSize
)) {
1730 // Patterns other than colors and surfaces are currently not accelerated.
1735 bool DrawTargetWebgl::DrawRect(const Rect
& aRect
, const Pattern
& aPattern
,
1736 const DrawOptions
& aOptions
,
1737 Maybe
<DeviceColor
> aMaskColor
,
1738 RefPtr
<TextureHandle
>* aHandle
,
1739 bool aTransformed
, bool aClipped
,
1740 bool aAccelOnly
, bool aForceUpdate
,
1741 const StrokeOptions
* aStrokeOptions
) {
1742 // If there is nothing to draw, then don't draw...
1743 if (aRect
.IsEmpty()) {
1747 // If we're already drawing directly to the WebGL context, then we want to
1748 // continue to do so. However, if we're drawing into a Skia layer over the
1749 // WebGL context, then we need to be careful to avoid repeatedly clearing
1750 // and flushing the layer if we hit a drawing request that can be accelerated
1751 // in between layered drawing requests, as clearing and flushing the layer
1752 // can be significantly expensive when repeated. So when a Skia layer is
1753 // active, if it is possible to continue drawing into the layer, then don't
1754 // accelerate the drawing request.
1755 if (mWebglValid
|| (mSkiaLayer
&& !mLayerDepth
&&
1756 (aAccelOnly
|| !SupportsLayering(aOptions
)))) {
1757 // If we get here, either the WebGL context is being directly drawn to
1758 // or we are going to flush the Skia layer to it before doing so. The shared
1759 // context still needs to be claimed and prepared for drawing. If this
1760 // fails, we just fall back to drawing with Skia below.
1761 if (PrepareContext(aClipped
)) {
1762 // The shared context is claimed and the framebuffer is now valid, so try
1763 // accelerated drawing.
1764 return mSharedContext
->DrawRectAccel(
1765 aRect
, aPattern
, aOptions
, aMaskColor
, aHandle
, aTransformed
,
1766 aClipped
, aAccelOnly
, aForceUpdate
, aStrokeOptions
);
1770 // Either there is no valid WebGL target to draw into, or we failed to prepare
1771 // it for drawing. The only thing we can do at this point is fall back to
1772 // drawing with Skia. If the request explicitly requires accelerated drawing,
1773 // then draw nothing before returning failure.
1775 DrawRectFallback(aRect
, aPattern
, aOptions
, aMaskColor
, aTransformed
,
1776 aClipped
, aStrokeOptions
);
1781 void DrawTargetWebgl::DrawRectFallback(const Rect
& aRect
,
1782 const Pattern
& aPattern
,
1783 const DrawOptions
& aOptions
,
1784 Maybe
<DeviceColor
> aMaskColor
,
1785 bool aTransformed
, bool aClipped
,
1786 const StrokeOptions
* aStrokeOptions
) {
1787 // Invalidate the WebGL target and prepare the Skia target for drawing.
1788 MarkSkiaChanged(aOptions
);
1791 // If transforms are requested, then just translate back to FillRect.
1793 mSkia
->Mask(ColorPattern(*aMaskColor
), aPattern
, aOptions
);
1794 } else if (aStrokeOptions
) {
1795 mSkia
->StrokeRect(aRect
, aPattern
, *aStrokeOptions
, aOptions
);
1797 mSkia
->FillRect(aRect
, aPattern
, aOptions
);
1799 } else if (aClipped
) {
1800 // If no transform was requested but clipping is still required, then
1801 // temporarily reset the transform before translating to FillRect.
1802 mSkia
->SetTransform(Matrix());
1804 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
1805 if (surfacePattern
.mSamplingRect
.IsEmpty()) {
1806 mSkia
->MaskSurface(ColorPattern(*aMaskColor
), surfacePattern
.mSurface
,
1807 aRect
.TopLeft(), aOptions
);
1809 mSkia
->Mask(ColorPattern(*aMaskColor
), aPattern
, aOptions
);
1811 } else if (aStrokeOptions
) {
1812 mSkia
->StrokeRect(aRect
, aPattern
, *aStrokeOptions
, aOptions
);
1814 mSkia
->FillRect(aRect
, aPattern
, aOptions
);
1816 mSkia
->SetTransform(mTransform
);
1817 } else if (aPattern
.GetType() == PatternType::SURFACE
) {
1818 // No transform nor clipping was requested, so it is essentially just a
1820 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
1821 mSkia
->CopySurface(surfacePattern
.mSurface
,
1822 surfacePattern
.mSurface
->GetRect(),
1823 IntPoint::Round(aRect
.TopLeft()));
1829 inline already_AddRefed
<WebGLTexture
> SharedContextWebgl::GetCompatibleSnapshot(
1830 SourceSurface
* aSurface
) const {
1831 if (aSurface
->GetType() == SurfaceType::WEBGL
) {
1832 RefPtr
<SourceSurfaceWebgl
> webglSurf
=
1833 static_cast<SourceSurfaceWebgl
*>(aSurface
);
1834 if (this == webglSurf
->mSharedContext
) {
1835 // If there is a snapshot copy in a texture handle, use that.
1836 if (webglSurf
->mHandle
) {
1838 webglSurf
->mHandle
->GetBackingTexture()->GetWebGLTexture());
1840 if (RefPtr
<DrawTargetWebgl
> webglDT
= webglSurf
->GetTarget()) {
1841 // If there is a copy-on-write reference to a target, use its backing
1842 // texture directly. This is only safe if the targets don't match, but
1843 // MarkChanged should ensure that any snapshots were copied into a
1844 // texture handle before we ever get here.
1845 if (!IsCurrentTarget(webglDT
)) {
1846 return do_AddRef(webglDT
->mTex
);
1854 inline bool SharedContextWebgl::IsCompatibleSurface(
1855 SourceSurface
* aSurface
) const {
1856 return bool(RefPtr
<WebGLTexture
>(GetCompatibleSnapshot(aSurface
)));
1859 bool SharedContextWebgl::UploadSurface(DataSourceSurface
* aData
,
1860 SurfaceFormat aFormat
,
1861 const IntRect
& aSrcRect
,
1862 const IntPoint
& aDstOffset
, bool aInit
,
1864 const RefPtr
<WebGLTexture
>& aTex
) {
1865 webgl::TexUnpackBlobDesc texDesc
= {
1866 LOCAL_GL_TEXTURE_2D
,
1867 {uint32_t(aSrcRect
.width
), uint32_t(aSrcRect
.height
), 1}};
1869 // The surface needs to be uploaded to its backing texture either to
1870 // initialize or update the texture handle contents. Map the data
1871 // contents of the surface so it can be read.
1872 DataSourceSurface::ScopedMap
map(aData
, DataSourceSurface::READ
);
1873 if (!map
.IsMapped()) {
1876 int32_t stride
= map
.GetStride();
1877 int32_t bpp
= BytesPerPixel(aFormat
);
1878 // Get the data pointer range considering the sampling rect offset and
1880 Span
<const uint8_t> range(
1881 map
.GetData() + aSrcRect
.y
* size_t(stride
) + aSrcRect
.x
* bpp
,
1882 std::max(aSrcRect
.height
- 1, 0) * size_t(stride
) +
1883 aSrcRect
.width
* bpp
);
1884 texDesc
.cpuData
= Some(range
);
1885 // If the stride happens to be 4 byte aligned, assume that is the
1886 // desired alignment regardless of format (even A8). Otherwise, we
1887 // default to byte alignment.
1888 texDesc
.unpacking
.alignmentInTypeElems
= stride
% 4 ? 1 : 4;
1889 texDesc
.unpacking
.rowLength
= stride
/ bpp
;
1891 // Create a PBO filled with zero data to initialize the texture data and
1892 // avoid slow initialization inside WebGL.
1893 MOZ_ASSERT(aSrcRect
.TopLeft() == IntPoint(0, 0));
1895 size_t(GetAlignedStride
<4>(aSrcRect
.width
, BytesPerPixel(aFormat
))) *
1897 if (!mZeroBuffer
|| size
> mZeroSize
) {
1898 mZeroBuffer
= mWebgl
->CreateBuffer();
1900 mWebgl
->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER
, mZeroBuffer
);
1901 // WebGL will zero initialize the empty buffer, so we don't send zero data
1903 mWebgl
->BufferData(LOCAL_GL_PIXEL_UNPACK_BUFFER
, size
, nullptr,
1904 LOCAL_GL_STATIC_DRAW
);
1906 mWebgl
->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER
, mZeroBuffer
);
1908 texDesc
.pboOffset
= Some(0);
1910 // Upload as RGBA8 to avoid swizzling during upload. Surfaces provide
1911 // data as BGRA, but we manually swizzle that in the shader. An A8
1912 // surface will be stored as an R8 texture that will also be swizzled
1915 aFormat
== SurfaceFormat::A8
? LOCAL_GL_R8
: LOCAL_GL_RGBA8
;
1917 aFormat
== SurfaceFormat::A8
? LOCAL_GL_RED
: LOCAL_GL_RGBA
;
1918 webgl::PackingInfo texPI
= {extFormat
, LOCAL_GL_UNSIGNED_BYTE
};
1919 // Do the (partial) upload for the shared or standalone texture.
1921 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, aTex
);
1923 mWebgl
->TexImage(0, aInit
? intFormat
: 0,
1924 {uint32_t(aDstOffset
.x
), uint32_t(aDstOffset
.y
), 0}, texPI
,
1927 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, mLastTexture
);
1929 if (!aData
&& aZero
) {
1930 mWebgl
->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER
, 0);
1935 // Allocate a new texture handle backed by either a standalone texture or as a
1936 // sub-texture of a larger shared texture.
1937 already_AddRefed
<TextureHandle
> SharedContextWebgl::AllocateTextureHandle(
1938 SurfaceFormat aFormat
, const IntSize
& aSize
, bool aAllowShared
,
1940 RefPtr
<TextureHandle
> handle
;
1941 // Calculate the bytes that would be used by this texture handle, and prune
1942 // enough other textures to ensure we have that much usable texture space
1943 // available to allocate.
1944 size_t usedBytes
= BackingTexture::UsedBytes(aFormat
, aSize
);
1945 PruneTextureMemory(usedBytes
, false);
1946 // The requested page size for shared textures.
1947 int32_t pageSize
= int32_t(std::min(
1948 StaticPrefs::gfx_canvas_accelerated_shared_page_size(), mMaxTextureSize
));
1949 if (aAllowShared
&& std::max(aSize
.width
, aSize
.height
) <= pageSize
/ 2) {
1950 // Ensure that the surface is no bigger than a quadrant of a shared texture
1951 // page. If so, try to allocate it to a shared texture. Look for any
1952 // existing shared texture page with a matching format and allocate
1953 // from that if possible.
1954 for (auto& shared
: mSharedTextures
) {
1955 if (shared
->GetFormat() == aFormat
&&
1956 shared
->IsRenderable() == aRenderable
) {
1957 bool wasEmpty
= !shared
->HasAllocatedHandles();
1958 handle
= shared
->Allocate(aSize
);
1961 // If the page was previously empty, then deduct it from the
1962 // empty memory reserves.
1963 mEmptyTextureMemory
-= shared
->UsedBytes();
1969 // If we couldn't find an existing shared texture page with matching
1970 // format, then allocate a new page to put the request in.
1972 if (RefPtr
<WebGLTexture
> tex
= mWebgl
->CreateTexture()) {
1973 RefPtr
<SharedTexture
> shared
=
1974 new SharedTexture(IntSize(pageSize
, pageSize
), aFormat
, tex
);
1976 shared
->MarkRenderable();
1978 mSharedTextures
.push_back(shared
);
1979 mTotalTextureMemory
+= shared
->UsedBytes();
1980 handle
= shared
->Allocate(aSize
);
1984 // The surface wouldn't fit in a shared texture page, so we need to
1985 // allocate a standalone texture for it instead.
1986 if (RefPtr
<WebGLTexture
> tex
= mWebgl
->CreateTexture()) {
1987 RefPtr
<StandaloneTexture
> standalone
=
1988 new StandaloneTexture(aSize
, aFormat
, tex
);
1990 standalone
->MarkRenderable();
1992 mStandaloneTextures
.push_back(standalone
);
1993 mTotalTextureMemory
+= standalone
->UsedBytes();
1994 handle
= standalone
;
2002 // Insert the new texture handle into the front of the MRU list and
2003 // update used space for it.
2004 mTextureHandles
.insertFront(handle
);
2005 ++mNumTextureHandles
;
2006 mUsedTextureMemory
+= handle
->UsedBytes();
2008 return handle
.forget();
2011 static inline SamplingFilter
GetSamplingFilter(const Pattern
& aPattern
) {
2012 return aPattern
.GetType() == PatternType::SURFACE
2013 ? static_cast<const SurfacePattern
&>(aPattern
).mSamplingFilter
2014 : SamplingFilter::GOOD
;
2017 static inline bool UseNearestFilter(const Pattern
& aPattern
) {
2018 return GetSamplingFilter(aPattern
) == SamplingFilter::POINT
;
2021 // Determine if the rectangle is still axis-aligned and pixel-aligned.
2022 static inline Maybe
<IntRect
> IsAlignedRect(bool aTransformed
,
2023 const Matrix
& aCurrentTransform
,
2024 const Rect
& aRect
) {
2025 if (!aTransformed
|| aCurrentTransform
.HasOnlyIntegerTranslation()) {
2026 auto intRect
= RoundedToInt(aRect
);
2027 if (aRect
.WithinEpsilonOf(Rect(intRect
), 1.0e-3f
)) {
2029 intRect
+= RoundedToInt(aCurrentTransform
.GetTranslation());
2031 return Some(intRect
);
2037 Maybe
<uint32_t> SharedContextWebgl::GetUniformLocation(
2038 const RefPtr
<WebGLProgram
>& aProg
, const std::string
& aName
) const {
2039 if (!aProg
|| !aProg
->LinkInfo()) {
2043 for (const auto& activeUniform
: aProg
->LinkInfo()->active
.activeUniforms
) {
2044 if (activeUniform
.block_index
!= -1) continue;
2046 auto locName
= activeUniform
.name
;
2047 const auto indexed
= webgl::ParseIndexed(locName
);
2049 locName
= indexed
->name
;
2052 const auto baseLength
= locName
.size();
2053 for (const auto& pair
: activeUniform
.locByIndex
) {
2055 locName
.erase(baseLength
); // Erase previous "[N]".
2057 locName
+= std::to_string(pair
.first
);
2060 if (locName
== aName
|| locName
== aName
+ "[0]") {
2061 return Some(pair
.second
);
2070 struct IsUniformDataValT
: std::false_type
{};
2072 struct IsUniformDataValT
<webgl::UniformDataVal
> : std::true_type
{};
2074 struct IsUniformDataValT
<float> : std::true_type
{};
2076 struct IsUniformDataValT
<int32_t> : std::true_type
{};
2078 struct IsUniformDataValT
<uint32_t> : std::true_type
{};
2080 template <typename T
, typename
= std::enable_if_t
<IsUniformDataValT
<T
>::value
>>
2081 inline Range
<const webgl::UniformDataVal
> AsUniformDataVal(
2082 const Range
<const T
>& data
) {
2083 return {data
.begin().template ReinterpretCast
<const webgl::UniformDataVal
>(),
2084 data
.end().template ReinterpretCast
<const webgl::UniformDataVal
>()};
2087 template <class T
, size_t N
>
2088 inline void SharedContextWebgl::UniformData(GLenum aFuncElemType
,
2089 const Maybe
<uint32_t>& aLoc
,
2090 const Array
<T
, N
>& aData
) {
2091 // We currently always pass false for transpose. If in the future we need
2092 // support for transpose then caching needs to take that in to account.
2093 mWebgl
->UniformData(*aLoc
, false,
2094 AsUniformDataVal(Range
<const T
>(Span
<const T
>(aData
))));
2097 template <class T
, size_t N
>
2098 void SharedContextWebgl::MaybeUniformData(GLenum aFuncElemType
,
2099 const Maybe
<uint32_t>& aLoc
,
2100 const Array
<T
, N
>& aData
,
2101 Maybe
<Array
<T
, N
>>& aCached
) {
2102 if (aCached
.isNothing() || !(*aCached
== aData
)) {
2103 aCached
= Some(aData
);
2104 UniformData(aFuncElemType
, aLoc
, aData
);
2108 inline void SharedContextWebgl::DrawQuad() {
2109 mWebgl
->DrawArraysInstanced(LOCAL_GL_TRIANGLE_FAN
, 0, 4, 1);
2112 void SharedContextWebgl::DrawTriangles(const PathVertexRange
& aRange
) {
2113 mWebgl
->DrawArraysInstanced(LOCAL_GL_TRIANGLES
, GLint(aRange
.mOffset
),
2114 GLsizei(aRange
.mLength
), 1);
2117 // Common rectangle and pattern drawing function shared by many DrawTarget
2118 // commands. If aMaskColor is specified, the provided surface pattern will be
2119 // treated as a mask. If aHandle is specified, then the surface pattern's
2120 // texture will be cached in the supplied handle, as opposed to using the
2121 // surface's user data. If aTransformed or aClipped are false, then transforms
2122 // and/or clipping will be disabled. If aAccelOnly is specified, then this
2123 // function will return before it would have otherwise drawn without
2124 // acceleration. If aForceUpdate is specified, then the provided texture handle
2125 // will be respecified with the provided surface.
2126 bool SharedContextWebgl::DrawRectAccel(
2127 const Rect
& aRect
, const Pattern
& aPattern
, const DrawOptions
& aOptions
,
2128 Maybe
<DeviceColor
> aMaskColor
, RefPtr
<TextureHandle
>* aHandle
,
2129 bool aTransformed
, bool aClipped
, bool aAccelOnly
, bool aForceUpdate
,
2130 const StrokeOptions
* aStrokeOptions
, const PathVertexRange
* aVertexRange
,
2131 const Matrix
* aRectXform
) {
2132 // If the rect or clip rect is empty, then there is nothing to draw.
2133 if (aRect
.IsEmpty() || mClipRect
.IsEmpty()) {
2137 // Check if the drawing options and the pattern support acceleration. Also
2138 // ensure the framebuffer is prepared for drawing. If not, fall back to using
2139 // the Skia target. When we need to forcefully update a texture, we must be
2140 // careful to override any pattern limits, as the caller ensures the pattern
2141 // is otherwise a supported type.
2142 if (!SupportsDrawOptions(aOptions
) ||
2143 (!aForceUpdate
&& !SupportsPattern(aPattern
)) || aStrokeOptions
||
2144 !mCurrentTarget
->MarkChanged()) {
2145 // If only accelerated drawing was requested, bail out without software
2146 // drawing fallback.
2148 MOZ_ASSERT(!aVertexRange
);
2149 mCurrentTarget
->DrawRectFallback(aRect
, aPattern
, aOptions
, aMaskColor
,
2150 aTransformed
, aClipped
, aStrokeOptions
);
2155 const Matrix
& currentTransform
= mCurrentTarget
->GetTransform();
2156 // rectXform only applies to the rect, but should not apply to the pattern,
2157 // as it might inadvertently alter the pattern.
2158 Matrix rectXform
= currentTransform
;
2160 rectXform
.PreMultiply(*aRectXform
);
2163 if (aOptions
.mCompositionOp
== CompositionOp::OP_SOURCE
&& aTransformed
&&
2165 (HasClipMask() || !rectXform
.PreservesAxisAlignedRectangles() ||
2166 !rectXform
.TransformBounds(aRect
).Contains(Rect(mClipAARect
)) ||
2167 (aPattern
.GetType() == PatternType::SURFACE
&&
2168 !IsAlignedRect(aTransformed
, rectXform
, aRect
)))) {
2169 // Clear outside the mask region for masks that are not bounded by clip.
2170 return DrawRectAccel(Rect(mClipRect
), ColorPattern(DeviceColor(0, 0, 0, 0)),
2171 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
,
2172 aOptions
.mAntialiasMode
),
2173 Nothing(), nullptr, false, aClipped
, aAccelOnly
) &&
2174 DrawRectAccel(aRect
, aPattern
,
2175 DrawOptions(aOptions
.mAlpha
, CompositionOp::OP_ADD
,
2176 aOptions
.mAntialiasMode
),
2177 aMaskColor
, aHandle
, aTransformed
, aClipped
,
2178 aAccelOnly
, aForceUpdate
, aStrokeOptions
, aVertexRange
,
2181 if (aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
&&
2182 aPattern
.GetType() == PatternType::SURFACE
&& !aMaskColor
) {
2183 // If the surface being drawn with clear is not a mask, then its contents
2184 // needs to be ignored. Just use a color pattern instead.
2185 return DrawRectAccel(aRect
, ColorPattern(DeviceColor(1, 1, 1, 1)), aOptions
,
2186 Nothing(), aHandle
, aTransformed
, aClipped
, aAccelOnly
,
2187 aForceUpdate
, aStrokeOptions
, aVertexRange
,
2191 // Set up the scissor test to reflect the clipping rectangle, if supplied.
2192 if (!mClipRect
.Contains(IntRect(IntPoint(), mViewportSize
))) {
2193 EnableScissor(mClipRect
);
2198 bool success
= false;
2200 // Now try to actually draw the pattern...
2201 switch (aPattern
.GetType()) {
2202 case PatternType::COLOR
: {
2203 if (!aVertexRange
) {
2204 // Only an uncached draw if not using the vertex cache.
2205 mCurrentTarget
->mProfile
.OnUncachedDraw();
2207 DeviceColor color
= PremultiplyColor(
2208 static_cast<const ColorPattern
&>(aPattern
).mColor
, aOptions
.mAlpha
);
2209 if (((color
.a
== 1.0f
&&
2210 aOptions
.mCompositionOp
== CompositionOp::OP_OVER
) ||
2211 aOptions
.mCompositionOp
== CompositionOp::OP_SOURCE
||
2212 aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
) &&
2213 !aStrokeOptions
&& !aVertexRange
&& !HasClipMask() &&
2214 mClipAARect
.IsEqualEdges(Rect(mClipRect
))) {
2215 // Certain color patterns can be mapped to scissored clears. The
2216 // composition op must effectively overwrite the destination, and the
2217 // transform must map to an axis-aligned integer rectangle.
2218 if (Maybe
<IntRect
> intRect
=
2219 IsAlignedRect(aTransformed
, rectXform
, aRect
)) {
2220 // Only use a clear if the area is larger than a quarter or the
2222 if (intRect
->Area() >=
2223 (mViewportSize
.width
/ 2) * (mViewportSize
.height
/ 2)) {
2224 if (!intRect
->Contains(mClipRect
)) {
2225 EnableScissor(intRect
->Intersect(mClipRect
));
2227 if (aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
) {
2229 PremultiplyColor(mCurrentTarget
->GetClearPattern().mColor
);
2231 mWebgl
->ClearColor(color
.b
, color
.g
, color
.r
, color
.a
);
2232 mWebgl
->Clear(LOCAL_GL_COLOR_BUFFER_BIT
);
2238 // Map the composition op to a WebGL blend mode, if possible.
2239 Maybe
<DeviceColor
> blendColor
;
2240 if (aOptions
.mCompositionOp
== CompositionOp::OP_SOURCE
||
2241 aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
) {
2242 // The source operator can support clipping and AA by emulating it with
2243 // the over op. Supply the color with blend state, and set the shader
2244 // color to white, to avoid needing dual-source blending.
2245 blendColor
= Some(color
);
2246 // Both source and clear operators should output a mask from the shader.
2247 color
= DeviceColor(1, 1, 1, 1);
2249 SetBlendState(aOptions
.mCompositionOp
, blendColor
);
2250 // Since it couldn't be mapped to a scissored clear, we need to use the
2251 // solid color shader with supplied transform.
2252 if (mLastProgram
!= mSolidProgram
) {
2253 mWebgl
->UseProgram(mSolidProgram
);
2254 mLastProgram
= mSolidProgram
;
2256 Array
<float, 2> viewportData
= {float(mViewportSize
.width
),
2257 float(mViewportSize
.height
)};
2258 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramViewport
, viewportData
,
2259 mSolidProgramUniformState
.mViewport
);
2261 // Generated paths provide their own AA as vertex alpha.
2262 Array
<float, 1> aaData
= {aVertexRange
? 0.0f
: 1.0f
};
2263 MaybeUniformData(LOCAL_GL_FLOAT
, mSolidProgramAA
, aaData
,
2264 mSolidProgramUniformState
.mAA
);
2266 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel
2268 Array
<float, 4> clipData
= {mClipAARect
.x
- 0.5f
, mClipAARect
.y
- 0.5f
,
2269 mClipAARect
.XMost() + 0.5f
,
2270 mClipAARect
.YMost() + 0.5f
};
2271 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramClipBounds
, clipData
,
2272 mSolidProgramUniformState
.mClipBounds
);
2274 Array
<float, 4> colorData
= {color
.b
, color
.g
, color
.r
, color
.a
};
2275 Matrix
xform(aRect
.width
, 0.0f
, 0.0f
, aRect
.height
, aRect
.x
, aRect
.y
);
2279 Array
<float, 6> xformData
= {xform
._11
, xform
._12
, xform
._21
,
2280 xform
._22
, xform
._31
, xform
._32
};
2281 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramTransform
, xformData
,
2282 mSolidProgramUniformState
.mTransform
);
2284 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramColor
, colorData
,
2285 mSolidProgramUniformState
.mColor
);
2287 // Finally draw the colored rectangle.
2289 // If there's a vertex range, then we need to draw triangles within from
2290 // generated from a path stored in the path vertex buffer.
2291 DrawTriangles(*aVertexRange
);
2293 // Otherwise we're drawing a simple filled rectangle.
2299 case PatternType::SURFACE
: {
2300 auto surfacePattern
= static_cast<const SurfacePattern
&>(aPattern
);
2301 // If a texture handle was supplied, or if the surface already has an
2302 // assigned texture handle stashed in its used data, try to use it.
2303 RefPtr
<TextureHandle
> handle
=
2304 aHandle
? aHandle
->get()
2305 : (surfacePattern
.mSurface
2306 ? static_cast<TextureHandle
*>(
2307 surfacePattern
.mSurface
->GetUserData(
2308 &mTextureHandleKey
))
2312 SurfaceFormat format
;
2313 // Check if the found handle is still valid and if its sampling rect
2314 // matches the requested sampling rect.
2315 if (handle
&& handle
->IsValid() &&
2316 (surfacePattern
.mSamplingRect
.IsEmpty() ||
2317 handle
->GetSamplingRect().IsEqualEdges(
2318 surfacePattern
.mSamplingRect
)) &&
2319 (surfacePattern
.mExtendMode
== ExtendMode::CLAMP
||
2320 handle
->GetType() == TextureHandle::STANDALONE
)) {
2321 texSize
= handle
->GetSize();
2322 format
= handle
->GetFormat();
2323 offset
= handle
->GetSamplingOffset();
2325 // Otherwise, there is no handle that can be used yet, so extract
2326 // information from the surface pattern.
2328 if (!surfacePattern
.mSurface
) {
2329 // If there was no actual surface supplied, then we tried to draw
2330 // using a texture handle, but the texture handle wasn't valid.
2333 texSize
= surfacePattern
.mSurface
->GetSize();
2334 format
= surfacePattern
.mSurface
->GetFormat();
2335 if (!surfacePattern
.mSamplingRect
.IsEmpty()) {
2336 texSize
= surfacePattern
.mSamplingRect
.Size();
2337 offset
= surfacePattern
.mSamplingRect
.TopLeft();
2341 // We need to be able to transform from local space into texture space.
2342 Matrix invMatrix
= surfacePattern
.mMatrix
;
2343 // If drawing a pre-transformed vertex range, then we need to ensure the
2344 // user-space pattern is still transformed to screen-space.
2345 if (aVertexRange
&& !aTransformed
) {
2346 invMatrix
*= currentTransform
;
2348 if (!invMatrix
.Invert()) {
2352 // If there is aRectXform, it must be applied to the source rectangle to
2353 // generate the proper input coordinates for the inverse pattern matrix.
2354 invMatrix
.PreMultiply(*aRectXform
);
2357 RefPtr
<WebGLTexture
> tex
;
2359 IntSize backingSize
;
2360 RefPtr
<DataSourceSurface
> data
;
2363 data
= surfacePattern
.mSurface
->GetDataSurface();
2367 // The size of the texture may change if we update contents.
2368 mUsedTextureMemory
-= handle
->UsedBytes();
2369 handle
->UpdateSize(texSize
);
2370 mUsedTextureMemory
+= handle
->UsedBytes();
2371 handle
->SetSamplingOffset(surfacePattern
.mSamplingRect
.TopLeft());
2373 // If using an existing handle, move it to the front of the MRU list.
2375 mTextureHandles
.insertFront(handle
);
2376 } else if ((tex
= GetCompatibleSnapshot(surfacePattern
.mSurface
))) {
2377 backingSize
= surfacePattern
.mSurface
->GetSize();
2378 bounds
= IntRect(offset
, texSize
);
2379 // Count reusing a snapshot texture (no readback) as a cache hit.
2380 mCurrentTarget
->mProfile
.OnCacheHit();
2382 // If we get here, we need a data surface for a texture upload.
2383 data
= surfacePattern
.mSurface
->GetDataSurface();
2387 // There is no existing handle. Try to allocate a new one. If the
2388 // surface size may change via a forced update, then don't allocate
2389 // from a shared texture page.
2390 handle
= AllocateTextureHandle(
2392 !aForceUpdate
&& surfacePattern
.mExtendMode
== ExtendMode::CLAMP
);
2397 // Link the handle to the surface's user data.
2398 handle
->SetSamplingOffset(surfacePattern
.mSamplingRect
.TopLeft());
2402 handle
->SetSurface(surfacePattern
.mSurface
);
2403 surfacePattern
.mSurface
->AddUserData(&mTextureHandleKey
, handle
.get(),
2408 // Map the composition op to a WebGL blend mode, if possible. If there is
2409 // a mask color and a texture with multiple channels, assume subpixel
2410 // blending. If we encounter the source op here, then assume the surface
2411 // is opaque (non-opaque is handled above) and emulate it with over.
2412 SetBlendState(aOptions
.mCompositionOp
,
2413 format
!= SurfaceFormat::A8
? aMaskColor
: Nothing());
2414 // Switch to the image shader and set up relevant transforms.
2415 if (mLastProgram
!= mImageProgram
) {
2416 mWebgl
->UseProgram(mImageProgram
);
2417 mLastProgram
= mImageProgram
;
2420 Array
<float, 2> viewportData
= {float(mViewportSize
.width
),
2421 float(mViewportSize
.height
)};
2422 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mImageProgramViewport
, viewportData
,
2423 mImageProgramUniformState
.mViewport
);
2425 // AA is not supported for OP_SOURCE. Generated paths provide their own
2426 // AA as vertex alpha.
2427 Array
<float, 1> aaData
= {
2428 mLastCompositionOp
== CompositionOp::OP_SOURCE
|| aVertexRange
2431 MaybeUniformData(LOCAL_GL_FLOAT
, mImageProgramAA
, aaData
,
2432 mImageProgramUniformState
.mAA
);
2434 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel
2436 Array
<float, 4> clipData
= {mClipAARect
.x
- 0.5f
, mClipAARect
.y
- 0.5f
,
2437 mClipAARect
.XMost() + 0.5f
,
2438 mClipAARect
.YMost() + 0.5f
};
2439 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mImageProgramClipBounds
, clipData
,
2440 mImageProgramUniformState
.mClipBounds
);
2443 mLastCompositionOp
== CompositionOp::OP_CLEAR
2444 ? DeviceColor(1, 1, 1, 1)
2446 aMaskColor
&& format
!= SurfaceFormat::A8
2447 ? DeviceColor::Mask(1.0f
, aMaskColor
->a
)
2448 : aMaskColor
.valueOr(DeviceColor(1, 1, 1, 1)),
2450 Array
<float, 4> colorData
= {color
.b
, color
.g
, color
.r
, color
.a
};
2451 Array
<float, 1> swizzleData
= {format
== SurfaceFormat::A8
? 1.0f
: 0.0f
};
2452 Matrix
xform(aRect
.width
, 0.0f
, 0.0f
, aRect
.height
, aRect
.x
, aRect
.y
);
2456 Array
<float, 6> xformData
= {xform
._11
, xform
._12
, xform
._21
,
2457 xform
._22
, xform
._31
, xform
._32
};
2458 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mImageProgramTransform
, xformData
,
2459 mImageProgramUniformState
.mTransform
);
2461 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mImageProgramColor
, colorData
,
2462 mImageProgramUniformState
.mColor
);
2464 MaybeUniformData(LOCAL_GL_FLOAT
, mImageProgramSwizzle
, swizzleData
,
2465 mImageProgramUniformState
.mSwizzle
);
2467 // Start binding the WebGL state for the texture.
2468 BackingTexture
* backing
= nullptr;
2470 backing
= handle
->GetBackingTexture();
2472 tex
= backing
->GetWebGLTexture();
2474 bounds
= handle
->GetBounds();
2475 backingSize
= backing
->GetSize();
2477 if (mLastTexture
!= tex
) {
2478 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, tex
);
2482 if (backing
&& !backing
->IsInitialized()) {
2483 // If this is the first time the texture is used, we need to initialize
2484 // the clamping and filtering state.
2485 backing
->MarkInitialized();
2486 InitTexParameters(tex
);
2487 if (texSize
!= backingSize
) {
2488 // If this is a shared texture handle whose actual backing texture is
2489 // larger than it, then we need to allocate the texture page to the
2490 // full backing size before we can do a partial upload of the surface.
2491 UploadSurface(nullptr, format
, IntRect(IntPoint(), backingSize
),
2492 IntPoint(), true, true);
2497 UploadSurface(data
, format
, IntRect(offset
, texSize
), bounds
.TopLeft(),
2498 texSize
== backingSize
);
2499 // Signal that we had to upload new data to the texture cache.
2500 mCurrentTarget
->mProfile
.OnCacheMiss();
2502 // Signal that we are reusing data from the texture cache.
2503 mCurrentTarget
->mProfile
.OnCacheHit();
2506 // Set up the texture coordinate matrix to map from the input rectangle to
2507 // the backing texture subrect.
2508 Size
backingSizeF(backingSize
);
2509 Matrix
uvMatrix(aRect
.width
, 0.0f
, 0.0f
, aRect
.height
, aRect
.x
, aRect
.y
);
2510 uvMatrix
*= invMatrix
;
2511 uvMatrix
*= Matrix(1.0f
/ backingSizeF
.width
, 0.0f
, 0.0f
,
2512 1.0f
/ backingSizeF
.height
,
2513 float(bounds
.x
- offset
.x
) / backingSizeF
.width
,
2514 float(bounds
.y
- offset
.y
) / backingSizeF
.height
);
2515 Array
<float, 6> uvData
= {uvMatrix
._11
, uvMatrix
._12
, uvMatrix
._21
,
2516 uvMatrix
._22
, uvMatrix
._31
, uvMatrix
._32
};
2517 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mImageProgramTexMatrix
, uvData
,
2518 mImageProgramUniformState
.mTexMatrix
);
2520 // Clamp sampling to within the bounds of the backing texture subrect.
2521 Array
<float, 4> texBounds
= {
2522 (bounds
.x
+ 0.5f
) / backingSizeF
.width
,
2523 (bounds
.y
+ 0.5f
) / backingSizeF
.height
,
2524 (bounds
.XMost() - 0.5f
) / backingSizeF
.width
,
2525 (bounds
.YMost() - 0.5f
) / backingSizeF
.height
,
2527 switch (surfacePattern
.mExtendMode
) {
2528 case ExtendMode::REPEAT
:
2529 texBounds
[0] = -1e16f
;
2530 texBounds
[1] = -1e16f
;
2531 texBounds
[2] = 1e16f
;
2532 texBounds
[3] = 1e16f
;
2534 case ExtendMode::REPEAT_X
:
2535 texBounds
[0] = -1e16f
;
2536 texBounds
[2] = 1e16f
;
2538 case ExtendMode::REPEAT_Y
:
2539 texBounds
[1] = -1e16f
;
2540 texBounds
[3] = 1e16f
;
2545 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mImageProgramTexBounds
, texBounds
,
2546 mImageProgramUniformState
.mTexBounds
);
2548 // Ensure we use nearest filtering when no antialiasing is requested.
2549 if (UseNearestFilter(surfacePattern
)) {
2550 SetTexFilter(tex
, false);
2553 // Finally draw the image rectangle.
2555 // If there's a vertex range, then we need to draw triangles within from
2556 // generated from a path stored in the path vertex buffer.
2557 DrawTriangles(*aVertexRange
);
2559 // Otherwise we're drawing a simple filled rectangle.
2563 // Restore the default linear filter if overridden.
2564 if (UseNearestFilter(surfacePattern
)) {
2565 SetTexFilter(tex
, true);
2572 gfxWarning() << "Unknown DrawTargetWebgl::DrawRect pattern type: "
2573 << (int)aPattern
.GetType();
2580 bool SharedContextWebgl::RemoveSharedTexture(
2581 const RefPtr
<SharedTexture
>& aTexture
) {
2583 std::find(mSharedTextures
.begin(), mSharedTextures
.end(), aTexture
);
2584 if (pos
== mSharedTextures
.end()) {
2587 // Keep around a reserve of empty pages to avoid initialization costs from
2588 // allocating shared pages. If still below the limit of reserved pages, then
2589 // just add it to the reserve. Otherwise, erase the empty texture page.
2590 size_t maxBytes
= StaticPrefs::gfx_canvas_accelerated_reserve_empty_cache()
2592 size_t usedBytes
= aTexture
->UsedBytes();
2593 if (mEmptyTextureMemory
+ usedBytes
<= maxBytes
) {
2594 mEmptyTextureMemory
+= usedBytes
;
2596 mTotalTextureMemory
-= usedBytes
;
2597 mSharedTextures
.erase(pos
);
2603 void SharedTextureHandle::Cleanup(SharedContextWebgl
& aContext
) {
2604 mTexture
->Free(*this);
2606 // Check if the shared handle's owning page has no more allocated handles
2607 // after we freed it. If so, remove the empty shared texture page also.
2608 if (!mTexture
->HasAllocatedHandles()) {
2609 aContext
.RemoveSharedTexture(mTexture
);
2613 bool SharedContextWebgl::RemoveStandaloneTexture(
2614 const RefPtr
<StandaloneTexture
>& aTexture
) {
2615 auto pos
= std::find(mStandaloneTextures
.begin(), mStandaloneTextures
.end(),
2617 if (pos
== mStandaloneTextures
.end()) {
2620 mTotalTextureMemory
-= aTexture
->UsedBytes();
2621 mStandaloneTextures
.erase(pos
);
2626 void StandaloneTexture::Cleanup(SharedContextWebgl
& aContext
) {
2627 aContext
.RemoveStandaloneTexture(this);
2630 // Prune a given texture handle and release its associated resources.
2631 void SharedContextWebgl::PruneTextureHandle(
2632 const RefPtr
<TextureHandle
>& aHandle
) {
2633 // Invalidate the handle so nothing will subsequently use its contents.
2634 aHandle
->Invalidate();
2635 // If the handle has an associated SourceSurface, unlink it.
2636 UnlinkSurfaceTexture(aHandle
);
2637 // If the handle has an associated CacheEntry, unlink it.
2638 if (RefPtr
<CacheEntry
> entry
= aHandle
->GetCacheEntry()) {
2641 // Deduct the used space from the total.
2642 mUsedTextureMemory
-= aHandle
->UsedBytes();
2643 // Ensure any allocated shared or standalone texture regions get freed.
2644 aHandle
->Cleanup(*this);
2647 // Prune any texture memory above the limit (or margin below the limit) or any
2648 // least-recently-used handles that are no longer associated with any usable
2650 bool SharedContextWebgl::PruneTextureMemory(size_t aMargin
, bool aPruneUnused
) {
2651 // The maximum amount of texture memory that may be used by textures.
2652 size_t maxBytes
= StaticPrefs::gfx_canvas_accelerated_cache_size() << 20;
2653 maxBytes
-= std::min(maxBytes
, aMargin
);
2654 size_t maxItems
= StaticPrefs::gfx_canvas_accelerated_cache_items();
2655 size_t oldItems
= mNumTextureHandles
;
2656 while (!mTextureHandles
.isEmpty() &&
2657 (mUsedTextureMemory
> maxBytes
|| mNumTextureHandles
> maxItems
||
2658 (aPruneUnused
&& !mTextureHandles
.getLast()->IsUsed()))) {
2659 PruneTextureHandle(mTextureHandles
.popLast());
2660 --mNumTextureHandles
;
2662 return mNumTextureHandles
< oldItems
;
2665 // Attempt to convert a linear gradient to a 1D ramp texture.
2666 Maybe
<SurfacePattern
> DrawTargetWebgl::LinearGradientToSurface(
2667 const RectDouble
& aBounds
, const Pattern
& aPattern
) {
2668 MOZ_ASSERT(aPattern
.GetType() == PatternType::LINEAR_GRADIENT
);
2669 const auto& gradient
= static_cast<const LinearGradientPattern
&>(aPattern
);
2670 // The gradient points must be transformed by the gradient's matrix.
2671 Point gradBegin
= gradient
.mMatrix
.TransformPoint(gradient
.mBegin
);
2672 Point gradEnd
= gradient
.mMatrix
.TransformPoint(gradient
.mEnd
);
2673 // Get the gradient points in user-space.
2674 Point begin
= mTransform
.TransformPoint(gradBegin
);
2675 Point end
= mTransform
.TransformPoint(gradEnd
);
2676 // Find the normalized direction of the gradient and its length.
2677 Point dir
= end
- begin
;
2678 float len
= dir
.Length();
2680 // Restrict the rendered bounds to fall within the canvas.
2681 Rect visBounds
= NarrowToFloat(aBounds
.SafeIntersect(RectDouble(GetRect())));
2682 // Calculate the distances along the gradient direction of the bounds.
2683 float dist0
= (visBounds
.TopLeft() - begin
).DotProduct(dir
);
2684 float distX
= visBounds
.width
* dir
.x
;
2685 float distY
= visBounds
.height
* dir
.y
;
2686 float minDist
= floorf(
2687 std::max(dist0
+ std::min(distX
, 0.0f
) + std::min(distY
, 0.0f
), 0.0f
));
2688 float maxDist
= ceilf(
2689 std::min(dist0
+ std::max(distX
, 0.0f
) + std::max(distY
, 0.0f
), len
));
2690 // Calculate the approximate size of the ramp texture, and see if it would be
2691 // sufficiently smaller than just rendering the primitive.
2692 float subLen
= maxDist
- minDist
;
2693 if (subLen
> 0 && subLen
< 0.5f
* visBounds
.Area()) {
2694 // Create a 1D texture to contain the gradient ramp. Reserve two extra
2695 // texels at the beginning and end of the ramp to account for clamping.
2696 RefPtr
<DrawTargetSkia
> dt
= new DrawTargetSkia
;
2697 if (dt
->Init(IntSize(int32_t(subLen
+ 2), 1), SurfaceFormat::B8G8R8A8
)) {
2698 // Fill the section of the gradient ramp that is actually used.
2699 dt
->FillRect(Rect(dt
->GetRect()),
2700 LinearGradientPattern(Point(1 - minDist
, 0.0f
),
2701 Point(len
+ 1 - minDist
, 0.0f
),
2703 if (RefPtr
<SourceSurface
> snapshot
= dt
->Snapshot()) {
2704 // Calculate a matrix that will map the gradient ramp texture onto the
2705 // actual direction of the gradient.
2706 Point gradDir
= (gradEnd
- gradBegin
) / len
;
2707 Point tangent
= Point(-gradDir
.y
, gradDir
.x
) / gradDir
.Length();
2708 SurfacePattern
surfacePattern(
2709 snapshot
, ExtendMode::CLAMP
,
2710 Matrix(gradDir
.x
, gradDir
.y
, tangent
.x
, tangent
.y
, gradBegin
.x
,
2712 .PreTranslate(minDist
- 1, 0));
2713 if (SupportsPattern(surfacePattern
)) {
2714 return Some(surfacePattern
);
2722 void DrawTargetWebgl::FillRect(const Rect
& aRect
, const Pattern
& aPattern
,
2723 const DrawOptions
& aOptions
) {
2724 RectDouble xformRect
= TransformDouble(aRect
);
2725 if (aPattern
.GetType() == PatternType::COLOR
) {
2726 if (Maybe
<Rect
> clipped
= RectClippedToViewport(xformRect
)) {
2727 // If the pattern is transform-invariant and the rect clips to the
2728 // viewport, just clip drawing to the viewport to avoid transform
2730 DrawRect(*clipped
, aPattern
, aOptions
, Nothing(), nullptr, false);
2734 if (RectInsidePrecisionLimits(xformRect
)) {
2735 if (SupportsPattern(aPattern
)) {
2736 DrawRect(aRect
, aPattern
, aOptions
);
2739 if (aPattern
.GetType() == PatternType::LINEAR_GRADIENT
) {
2740 if (Maybe
<SurfacePattern
> surface
=
2741 LinearGradientToSurface(xformRect
, aPattern
)) {
2742 if (DrawRect(aRect
, *surface
, aOptions
, Nothing(), nullptr, true, true,
2751 MarkSkiaChanged(aOptions
);
2752 mSkia
->FillRect(aRect
, aPattern
, aOptions
);
2754 // If the pattern is unsupported, then transform the rect to a path so it
2757 skiaPath
.addRect(RectToSkRect(aRect
));
2758 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
2759 DrawPath(path
, aPattern
, aOptions
);
2763 void CacheEntry::Link(const RefPtr
<TextureHandle
>& aHandle
) {
2765 mHandle
->SetCacheEntry(this);
2768 // When the CacheEntry becomes unused, it marks the corresponding
2769 // TextureHandle as unused and unlinks it from the CacheEntry. The
2770 // entry is removed from its containing Cache, if applicable.
2771 void CacheEntry::Unlink() {
2772 // The entry may not have a valid handle if rasterization failed.
2774 mHandle
->SetCacheEntry(nullptr);
2781 // Hashes a path and pattern to a single hash value that can be used for quick
2782 // comparisons. This currently avoids to expensive hashing of internal path
2783 // and pattern data for speed, relying instead on later exact comparisons for
2785 HashNumber
PathCacheEntry::HashPath(const QuantizedPath
& aPath
,
2786 const Pattern
* aPattern
,
2787 const Matrix
& aTransform
,
2788 const IntRect
& aBounds
,
2789 const Point
& aOrigin
) {
2790 HashNumber hash
= 0;
2791 hash
= AddToHash(hash
, aPath
.mPath
.num_types
);
2792 hash
= AddToHash(hash
, aPath
.mPath
.num_points
);
2793 if (aPath
.mPath
.num_points
> 0) {
2794 hash
= AddToHash(hash
, aPath
.mPath
.points
[0].x
);
2795 hash
= AddToHash(hash
, aPath
.mPath
.points
[0].y
);
2796 if (aPath
.mPath
.num_points
> 1) {
2797 hash
= AddToHash(hash
, aPath
.mPath
.points
[1].x
);
2798 hash
= AddToHash(hash
, aPath
.mPath
.points
[1].y
);
2801 // Quantize the relative offset of the path to its bounds.
2802 IntPoint offset
= RoundedToInt((aOrigin
- Point(aBounds
.TopLeft())) * 16.0f
);
2803 hash
= AddToHash(hash
, offset
.x
);
2804 hash
= AddToHash(hash
, offset
.y
);
2805 hash
= AddToHash(hash
, aBounds
.width
);
2806 hash
= AddToHash(hash
, aBounds
.height
);
2808 hash
= AddToHash(hash
, (int)aPattern
->GetType());
2813 // When caching rendered geometry, we need to ensure the scale and orientation
2814 // is approximately the same. The offset will be considered separately.
2815 static inline bool HasMatchingScale(const Matrix
& aTransform1
,
2816 const Matrix
& aTransform2
) {
2817 return FuzzyEqual(aTransform1
._11
, aTransform2
._11
) &&
2818 FuzzyEqual(aTransform1
._22
, aTransform2
._22
) &&
2819 FuzzyEqual(aTransform1
._12
, aTransform2
._12
) &&
2820 FuzzyEqual(aTransform1
._21
, aTransform2
._21
);
2823 // Determines if an existing path cache entry matches an incoming path and
2825 inline bool PathCacheEntry::MatchesPath(
2826 const QuantizedPath
& aPath
, const Pattern
* aPattern
,
2827 const StrokeOptions
* aStrokeOptions
, AAStrokeMode aStrokeMode
,
2828 const Matrix
& aTransform
, const IntRect
& aBounds
, const Point
& aOrigin
,
2829 HashNumber aHash
, float aSigma
) {
2830 return aHash
== mHash
&& HasMatchingScale(aTransform
, mTransform
) &&
2831 // Ensure the clipped relative bounds fit inside those of the entry
2832 aBounds
.x
- aOrigin
.x
>= mBounds
.x
- mOrigin
.x
&&
2833 (aBounds
.x
- aOrigin
.x
) + aBounds
.width
<=
2834 (mBounds
.x
- mOrigin
.x
) + mBounds
.width
&&
2835 aBounds
.y
- aOrigin
.y
>= mBounds
.y
- mOrigin
.y
&&
2836 (aBounds
.y
- aOrigin
.y
) + aBounds
.height
<=
2837 (mBounds
.y
- mOrigin
.y
) + mBounds
.height
&&
2839 (!aPattern
? !mPattern
: mPattern
&& *aPattern
== *mPattern
) &&
2842 : mStrokeOptions
&& *aStrokeOptions
== *mStrokeOptions
&&
2843 mAAStrokeMode
== aStrokeMode
) &&
2847 PathCacheEntry::PathCacheEntry(QuantizedPath
&& aPath
, Pattern
* aPattern
,
2848 StoredStrokeOptions
* aStrokeOptions
,
2849 AAStrokeMode aStrokeMode
,
2850 const Matrix
& aTransform
, const IntRect
& aBounds
,
2851 const Point
& aOrigin
, HashNumber aHash
,
2853 : CacheEntryImpl
<PathCacheEntry
>(aTransform
, aBounds
, aHash
),
2854 mPath(std::move(aPath
)),
2857 mStrokeOptions(aStrokeOptions
),
2858 mAAStrokeMode(aStrokeMode
),
2861 // Attempt to find a matching entry in the path cache. If one isn't found,
2862 // a new entry will be created. The caller should check whether the contained
2863 // texture handle is valid to determine if it will need to render the text run
2864 // or just reuse the cached texture.
2865 already_AddRefed
<PathCacheEntry
> PathCache::FindOrInsertEntry(
2866 QuantizedPath aPath
, const Pattern
* aPattern
,
2867 const StrokeOptions
* aStrokeOptions
, AAStrokeMode aStrokeMode
,
2868 const Matrix
& aTransform
, const IntRect
& aBounds
, const Point
& aOrigin
,
2871 PathCacheEntry::HashPath(aPath
, aPattern
, aTransform
, aBounds
, aOrigin
);
2872 for (const RefPtr
<PathCacheEntry
>& entry
: GetChain(hash
)) {
2873 if (entry
->MatchesPath(aPath
, aPattern
, aStrokeOptions
, aStrokeMode
,
2874 aTransform
, aBounds
, aOrigin
, hash
, aSigma
)) {
2875 return do_AddRef(entry
);
2878 Pattern
* pattern
= nullptr;
2880 pattern
= aPattern
->CloneWeak();
2885 StoredStrokeOptions
* strokeOptions
= nullptr;
2886 if (aStrokeOptions
) {
2887 strokeOptions
= aStrokeOptions
->Clone();
2888 if (!strokeOptions
) {
2892 RefPtr
<PathCacheEntry
> entry
=
2893 new PathCacheEntry(std::move(aPath
), pattern
, strokeOptions
, aStrokeMode
,
2894 aTransform
, aBounds
, aOrigin
, hash
, aSigma
);
2896 return entry
.forget();
2899 void DrawTargetWebgl::Fill(const Path
* aPath
, const Pattern
& aPattern
,
2900 const DrawOptions
& aOptions
) {
2901 if (!aPath
|| aPath
->GetBackendType() != BackendType::SKIA
) {
2905 const SkPath
& skiaPath
= static_cast<const PathSkia
*>(aPath
)->GetPath();
2906 SkRect skiaRect
= SkRect::MakeEmpty();
2907 // Draw the path as a simple rectangle with a supported pattern when possible.
2908 if (skiaPath
.isRect(&skiaRect
)) {
2909 RectDouble rect
= SkRectToRectDouble(skiaRect
);
2910 RectDouble xformRect
= TransformDouble(rect
);
2911 if (aPattern
.GetType() == PatternType::COLOR
) {
2912 if (Maybe
<Rect
> clipped
= RectClippedToViewport(xformRect
)) {
2913 // If the pattern is transform-invariant and the rect clips to the
2914 // viewport, just clip drawing to the viewport to avoid transform
2916 DrawRect(*clipped
, aPattern
, aOptions
, Nothing(), nullptr, false);
2921 if (RectInsidePrecisionLimits(xformRect
)) {
2922 if (SupportsPattern(aPattern
)) {
2923 DrawRect(NarrowToFloat(rect
), aPattern
, aOptions
);
2926 if (aPattern
.GetType() == PatternType::LINEAR_GRADIENT
) {
2927 if (Maybe
<SurfacePattern
> surface
=
2928 LinearGradientToSurface(xformRect
, aPattern
)) {
2929 if (DrawRect(NarrowToFloat(rect
), *surface
, aOptions
, Nothing(),
2930 nullptr, true, true, true)) {
2938 DrawPath(aPath
, aPattern
, aOptions
);
2941 void DrawTargetWebgl::FillCircle(const Point
& aOrigin
, float aRadius
,
2942 const Pattern
& aPattern
,
2943 const DrawOptions
& aOptions
) {
2944 DrawCircle(aOrigin
, aRadius
, aPattern
, aOptions
);
2947 QuantizedPath::QuantizedPath(const WGR::Path
& aPath
) : mPath(aPath
) {}
2949 QuantizedPath::QuantizedPath(QuantizedPath
&& aPath
) noexcept
2950 : mPath(aPath
.mPath
) {
2951 aPath
.mPath
.points
= nullptr;
2952 aPath
.mPath
.num_points
= 0;
2953 aPath
.mPath
.types
= nullptr;
2954 aPath
.mPath
.num_types
= 0;
2957 QuantizedPath::~QuantizedPath() {
2958 if (mPath
.points
|| mPath
.types
) {
2959 WGR::wgr_path_release(mPath
);
2963 bool QuantizedPath::operator==(const QuantizedPath
& aOther
) const {
2964 return mPath
.num_types
== aOther
.mPath
.num_types
&&
2965 mPath
.num_points
== aOther
.mPath
.num_points
&&
2966 mPath
.fill_mode
== aOther
.mPath
.fill_mode
&&
2967 !memcmp(mPath
.types
, aOther
.mPath
.types
,
2968 mPath
.num_types
* sizeof(uint8_t)) &&
2969 !memcmp(mPath
.points
, aOther
.mPath
.points
,
2970 mPath
.num_points
* sizeof(WGR::Point
));
2973 // Generate a quantized path from the Skia path using WGR. The supplied
2974 // transform will be applied to the path. The path is stored relative to its
2975 // bounds origin to support translation later.
2976 static Maybe
<QuantizedPath
> GenerateQuantizedPath(
2977 WGR::PathBuilder
* aPathBuilder
, const SkPath
& aPath
, const Rect
& aBounds
,
2978 const Matrix
& aTransform
) {
2979 if (!aPathBuilder
) {
2983 WGR::wgr_builder_reset(aPathBuilder
);
2984 WGR::wgr_builder_set_fill_mode(aPathBuilder
,
2985 aPath
.getFillType() == SkPathFillType::kWinding
2986 ? WGR::FillMode::Winding
2987 : WGR::FillMode::EvenOdd
);
2989 SkPath::RawIter
iter(aPath
);
2991 SkPath::Verb currentVerb
;
2993 // printf_stderr("bounds: (%d, %d) %d x %d\n", aBounds.x, aBounds.y,
2994 // aBounds.width, aBounds.height);
2995 Matrix transform
= aTransform
;
2996 transform
.PostTranslate(-aBounds
.TopLeft());
2997 while ((currentVerb
= iter
.next(params
)) != SkPath::kDone_Verb
) {
2998 switch (currentVerb
) {
2999 case SkPath::kMove_Verb
: {
3000 Point p0
= transform
.TransformPoint(SkPointToPoint(params
[0]));
3001 WGR::wgr_builder_move_to(aPathBuilder
, p0
.x
, p0
.y
);
3004 case SkPath::kLine_Verb
: {
3005 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
3006 WGR::wgr_builder_line_to(aPathBuilder
, p1
.x
, p1
.y
);
3009 case SkPath::kCubic_Verb
: {
3010 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
3011 Point p2
= transform
.TransformPoint(SkPointToPoint(params
[2]));
3012 Point p3
= transform
.TransformPoint(SkPointToPoint(params
[3]));
3013 // printf_stderr("cubic (%f, %f), (%f, %f), (%f, %f)\n", p1.x, p1.y,
3014 // p2.x, p2.y, p3.x, p3.y);
3015 WGR::wgr_builder_curve_to(aPathBuilder
, p1
.x
, p1
.y
, p2
.x
, p2
.y
, p3
.x
,
3019 case SkPath::kQuad_Verb
: {
3020 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
3021 Point p2
= transform
.TransformPoint(SkPointToPoint(params
[2]));
3022 // printf_stderr("quad (%f, %f), (%f, %f)\n", p1.x, p1.y, p2.x, p2.y);
3023 WGR::wgr_builder_quad_to(aPathBuilder
, p1
.x
, p1
.y
, p2
.x
, p2
.y
);
3026 case SkPath::kConic_Verb
: {
3027 Point p0
= transform
.TransformPoint(SkPointToPoint(params
[0]));
3028 Point p1
= transform
.TransformPoint(SkPointToPoint(params
[1]));
3029 Point p2
= transform
.TransformPoint(SkPointToPoint(params
[2]));
3030 float w
= iter
.conicWeight();
3031 std::vector
<Point
> quads
;
3032 int numQuads
= ConvertConicToQuads(p0
, p1
, p2
, w
, quads
);
3033 for (int i
= 0; i
< numQuads
; i
++) {
3034 Point q1
= quads
[2 * i
+ 1];
3035 Point q2
= quads
[2 * i
+ 2];
3036 // printf_stderr("conic quad (%f, %f), (%f, %f)\n", q1.x, q1.y, q2.x,
3038 WGR::wgr_builder_quad_to(aPathBuilder
, q1
.x
, q1
.y
, q2
.x
, q2
.y
);
3042 case SkPath::kClose_Verb
:
3043 // printf_stderr("close\n");
3044 WGR::wgr_builder_close(aPathBuilder
);
3048 // Unexpected verb found in path!
3053 WGR::Path p
= WGR::wgr_builder_get_path(aPathBuilder
);
3054 if (!p
.num_points
|| !p
.num_types
) {
3055 WGR::wgr_path_release(p
);
3058 return Some(QuantizedPath(p
));
3061 // Get the output vertex buffer using WGR from an input quantized path.
3062 static Maybe
<WGR::VertexBuffer
> GeneratePathVertexBuffer(
3063 const QuantizedPath
& aPath
, const IntRect
& aClipRect
,
3064 bool aRasterizationTruncates
, WGR::OutputVertex
* aBuffer
,
3065 size_t aBufferCapacity
) {
3066 WGR::VertexBuffer vb
= WGR::wgr_path_rasterize_to_tri_list(
3067 &aPath
.mPath
, aClipRect
.x
, aClipRect
.y
, aClipRect
.width
, aClipRect
.height
,
3068 true, false, aRasterizationTruncates
, aBuffer
, aBufferCapacity
);
3069 if (!vb
.len
|| (aBuffer
&& vb
.len
> aBufferCapacity
)) {
3070 WGR::wgr_vertex_buffer_release(vb
);
3076 static inline AAStroke::LineJoin
ToAAStrokeLineJoin(JoinStyle aJoin
) {
3078 case JoinStyle::BEVEL
:
3079 return AAStroke::LineJoin::Bevel
;
3080 case JoinStyle::ROUND
:
3081 return AAStroke::LineJoin::Round
;
3082 case JoinStyle::MITER
:
3083 case JoinStyle::MITER_OR_BEVEL
:
3084 return AAStroke::LineJoin::Miter
;
3086 return AAStroke::LineJoin::Miter
;
3089 static inline AAStroke::LineCap
ToAAStrokeLineCap(CapStyle aCap
) {
3091 case CapStyle::BUTT
:
3092 return AAStroke::LineCap::Butt
;
3093 case CapStyle::ROUND
:
3094 return AAStroke::LineCap::Round
;
3095 case CapStyle::SQUARE
:
3096 return AAStroke::LineCap::Square
;
3098 return AAStroke::LineCap::Butt
;
3101 static inline Point
WGRPointToPoint(const WGR::Point
& aPoint
) {
3102 // WGR points are 28.4 fixed-point where (0.0, 0.0) is assumed to be a pixel
3103 // center, as opposed to (0.5, 0.5) in canvas device space. WGR thus shifts
3104 // each point by (-0.5, -0.5). To undo this, transform from fixed-point back
3105 // to floating-point, and reverse the pixel shift by adding back (0.5, 0.5).
3106 return Point(IntPoint(aPoint
.x
, aPoint
.y
)) * (1.0f
/ 16.0f
) +
3110 // Generates a vertex buffer for a stroked path using aa-stroke.
3111 static Maybe
<AAStroke::VertexBuffer
> GenerateStrokeVertexBuffer(
3112 const QuantizedPath
& aPath
, const StrokeOptions
* aStrokeOptions
,
3113 float aScale
, WGR::OutputVertex
* aBuffer
, size_t aBufferCapacity
) {
3114 AAStroke::StrokeStyle style
= {aStrokeOptions
->mLineWidth
* aScale
,
3115 ToAAStrokeLineCap(aStrokeOptions
->mLineCap
),
3116 ToAAStrokeLineJoin(aStrokeOptions
->mLineJoin
),
3117 aStrokeOptions
->mMiterLimit
};
3118 if (style
.width
<= 0.0f
|| !std::isfinite(style
.width
) ||
3119 style
.miter_limit
<= 0.0f
|| !std::isfinite(style
.miter_limit
)) {
3122 AAStroke::Stroker
* s
= AAStroke::aa_stroke_new(
3123 &style
, (AAStroke::OutputVertex
*)aBuffer
, aBufferCapacity
);
3125 size_t curPoint
= 0;
3126 for (size_t curType
= 0; valid
&& curType
< aPath
.mPath
.num_types
;) {
3127 // Verify that we are at the start of a sub-path.
3128 if ((aPath
.mPath
.types
[curType
] & WGR::PathPointTypePathTypeMask
) !=
3129 WGR::PathPointTypeStart
) {
3133 // Find where the next sub-path starts so we can locate the end.
3134 size_t endType
= curType
+ 1;
3135 for (; endType
< aPath
.mPath
.num_types
; endType
++) {
3136 if ((aPath
.mPath
.types
[endType
] & WGR::PathPointTypePathTypeMask
) ==
3137 WGR::PathPointTypeStart
) {
3141 // Check if the path is closed. This is a flag modifying the last type.
3143 (aPath
.mPath
.types
[endType
- 1] & WGR::PathPointTypeCloseSubpath
) != 0;
3144 for (; curType
< endType
; curType
++) {
3145 // If this is the last type and the sub-path is not closed, determine if
3146 // this segment should be capped.
3147 bool end
= curType
+ 1 == endType
&& !closed
;
3148 switch (aPath
.mPath
.types
[curType
] & WGR::PathPointTypePathTypeMask
) {
3149 case WGR::PathPointTypeStart
: {
3150 if (curPoint
+ 1 > aPath
.mPath
.num_points
) {
3154 Point p1
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
]);
3155 AAStroke::aa_stroke_move_to(s
, p1
.x
, p1
.y
, closed
);
3157 AAStroke::aa_stroke_line_to(s
, p1
.x
, p1
.y
, true);
3162 case WGR::PathPointTypeLine
: {
3163 if (curPoint
+ 1 > aPath
.mPath
.num_points
) {
3167 Point p1
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
]);
3168 AAStroke::aa_stroke_line_to(s
, p1
.x
, p1
.y
, end
);
3172 case WGR::PathPointTypeBezier
: {
3173 if (curPoint
+ 3 > aPath
.mPath
.num_points
) {
3177 Point p1
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
]);
3178 Point p2
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
+ 1]);
3179 Point p3
= WGRPointToPoint(aPath
.mPath
.points
[curPoint
+ 2]);
3180 AAStroke::aa_stroke_curve_to(s
, p1
.x
, p1
.y
, p2
.x
, p2
.y
, p3
.x
, p3
.y
,
3186 MOZ_ASSERT(false, "Unknown WGR path point type");
3191 // Close the sub-path if necessary.
3192 if (valid
&& closed
) {
3193 AAStroke::aa_stroke_close(s
);
3196 Maybe
<AAStroke::VertexBuffer
> result
;
3198 AAStroke::VertexBuffer vb
= AAStroke::aa_stroke_finish(s
);
3199 if (!vb
.len
|| (aBuffer
&& vb
.len
> aBufferCapacity
)) {
3200 AAStroke::aa_stroke_vertex_buffer_release(vb
);
3205 AAStroke::aa_stroke_release(s
);
3209 // Search the path cache for any entries stored in the path vertex buffer and
3211 void PathCache::ClearVertexRanges() {
3212 for (auto& chain
: mChains
) {
3213 PathCacheEntry
* entry
= chain
.getFirst();
3215 PathCacheEntry
* next
= entry
->getNext();
3216 if (entry
->GetVertexRange().IsValid()) {
3224 inline bool DrawTargetWebgl::ShouldAccelPath(
3225 const DrawOptions
& aOptions
, const StrokeOptions
* aStrokeOptions
) {
3226 return mWebglValid
&& SupportsDrawOptions(aOptions
) && PrepareContext();
3229 // For now, we only directly support stroking solid color patterns to limit
3230 // artifacts from blending of overlapping geometry generated by AAStroke. Other
3231 // types of patterns may be partially supported by rendering to a temporary
3233 static inline AAStrokeMode
SupportsAAStroke(const Pattern
& aPattern
,
3234 const DrawOptions
& aOptions
,
3235 const StrokeOptions
& aStrokeOptions
,
3236 bool aAllowStrokeAlpha
) {
3237 if (aStrokeOptions
.mDashPattern
) {
3238 return AAStrokeMode::Unsupported
;
3240 switch (aOptions
.mCompositionOp
) {
3241 case CompositionOp::OP_SOURCE
:
3242 return AAStrokeMode::Geometry
;
3243 case CompositionOp::OP_OVER
:
3244 if (aPattern
.GetType() == PatternType::COLOR
) {
3245 return static_cast<const ColorPattern
&>(aPattern
).mColor
.a
*
3249 ? AAStrokeMode::Mask
3250 : AAStrokeMode::Geometry
;
3252 return AAStrokeMode::Unsupported
;
3254 return AAStrokeMode::Unsupported
;
3258 // Render an AA-Stroke'd vertex range into an R8 mask texture for subsequent
3260 already_AddRefed
<TextureHandle
> SharedContextWebgl::DrawStrokeMask(
3261 const PathVertexRange
& aVertexRange
, const IntSize
& aSize
) {
3262 // Allocate a new texture handle to store the rendered mask.
3263 RefPtr
<TextureHandle
> handle
=
3264 AllocateTextureHandle(SurfaceFormat::A8
, aSize
, true, true);
3269 IntRect texBounds
= handle
->GetBounds();
3270 BackingTexture
* backing
= handle
->GetBackingTexture();
3271 if (!backing
->IsInitialized()) {
3272 // If the backing texture is uninitialized, it needs its sampling parameters
3273 // set for later use.
3274 mWebgl
->BindTexture(LOCAL_GL_TEXTURE_2D
, backing
->GetWebGLTexture());
3275 mWebgl
->TexStorage(LOCAL_GL_TEXTURE_2D
, 1, LOCAL_GL_R8
,
3276 {uint32_t(backing
->GetSize().width
),
3277 uint32_t(backing
->GetSize().height
), 1});
3278 InitTexParameters(backing
->GetWebGLTexture());
3282 // Set up a scratch framebuffer to render to the appropriate sub-texture of
3283 // the backing texture.
3284 if (!mScratchFramebuffer
) {
3285 mScratchFramebuffer
= mWebgl
->CreateFramebuffer();
3287 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mScratchFramebuffer
);
3288 webgl::FbAttachInfo attachInfo
;
3289 attachInfo
.tex
= backing
->GetWebGLTexture();
3290 mWebgl
->FramebufferAttach(LOCAL_GL_FRAMEBUFFER
, LOCAL_GL_COLOR_ATTACHMENT0
,
3291 LOCAL_GL_TEXTURE_2D
, attachInfo
);
3292 mWebgl
->Viewport(texBounds
.x
, texBounds
.y
, texBounds
.width
, texBounds
.height
);
3293 EnableScissor(texBounds
);
3294 if (!backing
->IsInitialized()) {
3295 backing
->MarkInitialized();
3296 // WebGL implicitly clears the backing texture the first time it is used.
3298 // Ensure the mask background is clear.
3299 mWebgl
->ClearColor(0.0f
, 0.0f
, 0.0f
, 0.0f
);
3300 mWebgl
->Clear(LOCAL_GL_COLOR_BUFFER_BIT
);
3303 // Reset any blending when drawing the mask.
3304 SetBlendState(CompositionOp::OP_OVER
);
3306 // Set up the solid color shader to draw a simple opaque mask.
3307 if (mLastProgram
!= mSolidProgram
) {
3308 mWebgl
->UseProgram(mSolidProgram
);
3309 mLastProgram
= mSolidProgram
;
3311 Array
<float, 2> viewportData
= {float(texBounds
.width
),
3312 float(texBounds
.height
)};
3313 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramViewport
, viewportData
,
3314 mSolidProgramUniformState
.mViewport
);
3315 Array
<float, 1> aaData
= {0.0f
};
3316 MaybeUniformData(LOCAL_GL_FLOAT
, mSolidProgramAA
, aaData
,
3317 mSolidProgramUniformState
.mAA
);
3318 Array
<float, 4> clipData
= {-0.5f
, -0.5f
, float(texBounds
.width
) + 0.5f
,
3319 float(texBounds
.height
) + 0.5f
};
3320 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramClipBounds
, clipData
,
3321 mSolidProgramUniformState
.mClipBounds
);
3322 Array
<float, 4> colorData
= {1.0f
, 1.0f
, 1.0f
, 1.0f
};
3323 MaybeUniformData(LOCAL_GL_FLOAT_VEC4
, mSolidProgramColor
, colorData
,
3324 mSolidProgramUniformState
.mColor
);
3325 Array
<float, 6> xformData
= {1.0f
, 0.0f
, 0.0f
, 1.0f
, 0.0f
, 0.0f
};
3326 MaybeUniformData(LOCAL_GL_FLOAT_VEC2
, mSolidProgramTransform
, xformData
,
3327 mSolidProgramUniformState
.mTransform
);
3329 // Ensure the current clip mask is ignored.
3330 RefPtr
<WebGLTexture
> prevClipMask
= mLastClipMask
;
3333 // Draw the mask using the supplied path vertex range.
3334 DrawTriangles(aVertexRange
);
3336 // Restore the previous framebuffer state.
3337 mWebgl
->BindFramebuffer(LOCAL_GL_FRAMEBUFFER
, mCurrentTarget
->mFramebuffer
);
3338 mWebgl
->Viewport(0, 0, mViewportSize
.width
, mViewportSize
.height
);
3340 SetClipMask(prevClipMask
);
3343 return handle
.forget();
3346 bool SharedContextWebgl::DrawPathAccel(
3347 const Path
* aPath
, const Pattern
& aPattern
, const DrawOptions
& aOptions
,
3348 const StrokeOptions
* aStrokeOptions
, bool aAllowStrokeAlpha
,
3349 const ShadowOptions
* aShadow
, bool aCacheable
, const Matrix
* aPathXform
) {
3350 // Get the transformed bounds for the path and conservatively check if the
3351 // bounds overlap the canvas.
3352 const PathSkia
* pathSkia
= static_cast<const PathSkia
*>(aPath
);
3353 const Matrix
& currentTransform
= mCurrentTarget
->GetTransform();
3354 Matrix pathXform
= currentTransform
;
3355 // If there is a path-specific transform that shouldn't be applied to the
3356 // pattern, then generate a matrix that should only be used with the Skia
3359 pathXform
.PreMultiply(*aPathXform
);
3361 Rect bounds
= pathSkia
->GetFastBounds(pathXform
, aStrokeOptions
);
3362 // If the path is empty, then there is nothing to draw.
3363 if (bounds
.IsEmpty()) {
3366 // Avoid integer conversion errors with abnormally large paths.
3367 if (!RectInsidePrecisionLimits(bounds
)) {
3370 IntRect
viewport(IntPoint(), mViewportSize
);
3372 // Inflate the bounds to account for the blur radius.
3373 bounds
+= aShadow
->mOffset
;
3374 int32_t blurRadius
= aShadow
->BlurRadius();
3375 bounds
.Inflate(blurRadius
);
3376 viewport
.Inflate(blurRadius
);
3378 Point realOrigin
= bounds
.TopLeft();
3380 // Quantize the path origin to increase the reuse of cache entries.
3383 bounds
.Scale(0.25f
);
3385 Point quantizedOrigin
= bounds
.TopLeft();
3386 // If the path doesn't intersect the viewport, then there is nothing to draw.
3387 IntRect intBounds
= RoundedOut(bounds
).Intersect(viewport
);
3388 if (intBounds
.IsEmpty()) {
3391 // Nudge the bounds to account for the quantization rounding.
3392 Rect quantBounds
= Rect(intBounds
) + (realOrigin
- quantizedOrigin
);
3393 // If the pattern is a solid color, then this will be used along with a path
3394 // mask to render the path, as opposed to baking the pattern into the cached
3396 Maybe
<DeviceColor
> color
=
3397 aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
3398 ? Some(DeviceColor(1, 1, 1, 1))
3399 : (aPattern
.GetType() == PatternType::COLOR
3400 ? Some(static_cast<const ColorPattern
&>(aPattern
).mColor
)
3402 AAStrokeMode aaStrokeMode
=
3403 aStrokeOptions
&& mPathAAStroke
3404 ? SupportsAAStroke(aPattern
, aOptions
, *aStrokeOptions
,
3406 : AAStrokeMode::Unsupported
;
3407 // Look for an existing path cache entry, if possible, or otherwise create
3408 // one. If the draw request is not cacheable, then don't create an entry.
3409 RefPtr
<PathCacheEntry
> entry
;
3410 RefPtr
<TextureHandle
> handle
;
3413 mPathCache
= MakeUnique
<PathCache
>();
3415 // Use a quantized, relative (to its bounds origin) version of the path as
3416 // a cache key to help limit cache bloat.
3417 Maybe
<QuantizedPath
> qp
= GenerateQuantizedPath(
3418 mWGRPathBuilder
, pathSkia
->GetPath(), quantBounds
, pathXform
);
3422 entry
= mPathCache
->FindOrInsertEntry(
3423 std::move(*qp
), color
? nullptr : &aPattern
, aStrokeOptions
,
3424 aaStrokeMode
, currentTransform
, intBounds
, quantizedOrigin
,
3425 aShadow
? aShadow
->mSigma
: -1.0f
);
3429 handle
= entry
->GetHandle();
3432 // If there is a shadow, it needs to draw with the shadow color rather than
3434 Maybe
<DeviceColor
> shadowColor
= color
;
3435 if (aShadow
&& aOptions
.mCompositionOp
!= CompositionOp::OP_CLEAR
) {
3436 shadowColor
= Some(aShadow
->mColor
);
3438 shadowColor
->a
*= color
->a
;
3441 SamplingFilter filter
=
3442 aShadow
? SamplingFilter::GOOD
: GetSamplingFilter(aPattern
);
3443 if (handle
&& handle
->IsValid()) {
3444 // If the entry has a valid texture handle still, use it. However, the
3445 // entry texture is assumed to be located relative to its previous bounds.
3446 // We need to offset the pattern by the difference between its new unclipped
3447 // origin and its previous previous unclipped origin. Then when we finally
3448 // draw a rectangle at the expected new bounds, it will overlap the portion
3449 // of the old entry texture we actually need to sample from.
3451 (realOrigin
- entry
->GetOrigin()) + entry
->GetBounds().TopLeft();
3452 SurfacePattern
pathPattern(nullptr, ExtendMode::CLAMP
,
3453 Matrix::Translation(offset
), filter
);
3454 return DrawRectAccel(quantBounds
, pathPattern
, aOptions
, shadowColor
,
3455 &handle
, false, true, true);
3458 if (mPathVertexCapacity
> 0 && !handle
&& entry
&& !aShadow
&&
3459 aOptions
.mAntialiasMode
!= AntialiasMode::NONE
&&
3460 SupportsPattern(aPattern
) &&
3461 entry
->GetPath().mPath
.num_types
<= mPathMaxComplexity
) {
3462 if (entry
->GetVertexRange().IsValid()) {
3463 // If there is a valid cached vertex data in the path vertex buffer, then
3464 // just draw that. We must draw at integer pixel boundaries (using
3465 // intBounds instead of quantBounds) due to WGR's reliance on pixel center
3467 mCurrentTarget
->mProfile
.OnCacheHit();
3468 return DrawRectAccel(Rect(intBounds
.TopLeft(), Size(1, 1)), aPattern
,
3469 aOptions
, Nothing(), nullptr, false, true, true,
3470 false, nullptr, &entry
->GetVertexRange());
3473 // printf_stderr("Generating... verbs %d, points %d\n",
3474 // int(pathSkia->GetPath().countVerbs()),
3475 // int(pathSkia->GetPath().countPoints()));
3476 WGR::OutputVertex
* outputBuffer
= nullptr;
3477 size_t outputBufferCapacity
= 0;
3478 if (mWGROutputBuffer
) {
3479 outputBuffer
= mWGROutputBuffer
.get();
3480 outputBufferCapacity
= mPathVertexCapacity
/ sizeof(WGR::OutputVertex
);
3482 Maybe
<WGR::VertexBuffer
> wgrVB
;
3483 Maybe
<AAStroke::VertexBuffer
> strokeVB
;
3484 if (!aStrokeOptions
) {
3485 if (aPath
== mUnitCirclePath
) {
3486 auto scaleFactors
= pathXform
.ScaleFactors();
3487 if (scaleFactors
.AreScalesSame()) {
3488 Point center
= pathXform
.GetTranslation() - quantBounds
.TopLeft();
3489 float radius
= scaleFactors
.xScale
;
3490 AAStroke::VertexBuffer vb
= AAStroke::aa_stroke_filled_circle(
3491 center
.x
, center
.y
, radius
, (AAStroke::OutputVertex
*)outputBuffer
,
3492 outputBufferCapacity
);
3493 if (!vb
.len
|| (outputBuffer
&& vb
.len
> outputBufferCapacity
)) {
3494 AAStroke::aa_stroke_vertex_buffer_release(vb
);
3496 strokeVB
= Some(vb
);
3501 wgrVB
= GeneratePathVertexBuffer(
3502 entry
->GetPath(), IntRect(-intBounds
.TopLeft(), mViewportSize
),
3503 mRasterizationTruncates
, outputBuffer
, outputBufferCapacity
);
3506 if (aaStrokeMode
!= AAStrokeMode::Unsupported
) {
3507 auto scaleFactors
= currentTransform
.ScaleFactors();
3508 if (scaleFactors
.AreScalesSame()) {
3509 strokeVB
= GenerateStrokeVertexBuffer(
3510 entry
->GetPath(), aStrokeOptions
, scaleFactors
.xScale
,
3511 outputBuffer
, outputBufferCapacity
);
3514 if (!strokeVB
&& mPathWGRStroke
) {
3515 // If stroking, then generate a path to fill the stroked region. This
3516 // path will need to be quantized again because it differs from the
3517 // path used for the cache entry, but this allows us to avoid
3518 // generating a fill path on a cache hit.
3519 Maybe
<Rect
> cullRect
;
3520 Matrix invTransform
= currentTransform
;
3521 if (invTransform
.Invert()) {
3522 // Transform the stroking clip rect from device space to local
3524 Rect invRect
= invTransform
.TransformBounds(Rect(mClipRect
));
3526 cullRect
= Some(invRect
);
3529 if (pathSkia
->GetFillPath(*aStrokeOptions
, pathXform
, fillPath
,
3531 // printf_stderr(" stroke fill... verbs %d, points %d\n",
3532 // int(fillPath.countVerbs()),
3533 // int(fillPath.countPoints()));
3534 if (Maybe
<QuantizedPath
> qp
= GenerateQuantizedPath(
3535 mWGRPathBuilder
, fillPath
, quantBounds
, pathXform
)) {
3536 wgrVB
= GeneratePathVertexBuffer(
3537 *qp
, IntRect(-intBounds
.TopLeft(), mViewportSize
),
3538 mRasterizationTruncates
, outputBuffer
, outputBufferCapacity
);
3543 if (wgrVB
|| strokeVB
) {
3544 const uint8_t* vbData
=
3545 wgrVB
? (const uint8_t*)wgrVB
->data
: (const uint8_t*)strokeVB
->data
;
3546 if (outputBuffer
&& !vbData
) {
3547 vbData
= (const uint8_t*)outputBuffer
;
3549 size_t vbLen
= wgrVB
? wgrVB
->len
: strokeVB
->len
;
3550 uint32_t vertexBytes
= uint32_t(
3551 std::min(vbLen
* sizeof(WGR::OutputVertex
), size_t(UINT32_MAX
)));
3552 // printf_stderr(" ... %d verts, %d bytes\n", int(vbLen),
3553 // int(vertexBytes));
3554 if (vertexBytes
> mPathVertexCapacity
- mPathVertexOffset
&&
3555 vertexBytes
<= mPathVertexCapacity
- sizeof(kRectVertexData
)) {
3556 // If the vertex data is too large to fit in the remaining path vertex
3557 // buffer, then orphan the contents of the vertex buffer to make room
3560 mPathCache
->ClearVertexRanges();
3562 ResetPathVertexBuffer(false);
3564 if (vertexBytes
<= mPathVertexCapacity
- mPathVertexOffset
) {
3565 // If there is actually room to fit the vertex data in the vertex buffer
3566 // after orphaning as necessary, then upload the data to the next
3567 // available offset in the buffer.
3568 PathVertexRange
vertexRange(
3569 uint32_t(mPathVertexOffset
/ sizeof(WGR::OutputVertex
)),
3571 // printf_stderr(" ... offset %d\n", mPathVertexOffset);
3572 // Normal glBufferSubData interleaved with draw calls causes performance
3573 // issues on Mali, so use our special unsynchronized version. This is
3574 // safe as we never update regions referenced by pending draw calls.
3575 mWebgl
->BufferSubData(LOCAL_GL_ARRAY_BUFFER
, mPathVertexOffset
,
3576 vertexBytes
, vbData
,
3577 /* unsynchronized */ true);
3578 mPathVertexOffset
+= vertexBytes
;
3580 WGR::wgr_vertex_buffer_release(wgrVB
.ref());
3582 AAStroke::aa_stroke_vertex_buffer_release(strokeVB
.ref());
3584 if (strokeVB
&& aaStrokeMode
== AAStrokeMode::Mask
) {
3585 // Attempt to generate a stroke mask for path.
3586 if (RefPtr
<TextureHandle
> handle
=
3587 DrawStrokeMask(vertexRange
, intBounds
.Size())) {
3588 // Finally, draw the rendered stroke mask.
3590 entry
->Link(handle
);
3592 mCurrentTarget
->mProfile
.OnCacheMiss();
3593 SurfacePattern
maskPattern(
3594 nullptr, ExtendMode::CLAMP
,
3595 Matrix::Translation(quantBounds
.TopLeft()),
3596 SamplingFilter::GOOD
);
3597 return DrawRectAccel(quantBounds
, maskPattern
, aOptions
, color
,
3598 &handle
, false, true, true);
3601 // Remember the vertex range in the cache entry so that it can be
3604 entry
->SetVertexRange(vertexRange
);
3607 // Finally, draw the uploaded vertex data.
3608 mCurrentTarget
->mProfile
.OnCacheMiss();
3609 return DrawRectAccel(Rect(intBounds
.TopLeft(), Size(1, 1)), aPattern
,
3610 aOptions
, Nothing(), nullptr, false, true, true,
3611 false, nullptr, &vertexRange
);
3615 WGR::wgr_vertex_buffer_release(wgrVB
.ref());
3617 AAStroke::aa_stroke_vertex_buffer_release(strokeVB
.ref());
3620 // If we failed to draw the vertex data for some reason, then fall through
3621 // to the texture rasterization path.
3625 // If a stroke path covers too much screen area, it is likely that most is
3626 // empty space in the interior. This usually imposes too high a cost versus
3627 // just rasterizing without acceleration. Note that AA-Stroke generally
3628 // produces more acceptable amounts of geometry for larger paths, so we do
3629 // this heuristic after we attempt AA-Stroke.
3630 if (aStrokeOptions
&&
3631 intBounds
.width
* intBounds
.height
>
3632 (mViewportSize
.width
/ 2) * (mViewportSize
.height
/ 2)) {
3636 // If there isn't a valid texture handle, then we need to rasterize the
3637 // path in a software canvas and upload this to a texture. Solid color
3638 // patterns will be rendered as a path mask that can then be modulated
3639 // with any color. Other pattern types have to rasterize the pattern
3640 // directly into the cached texture.
3642 RefPtr
<DrawTargetSkia
> pathDT
= new DrawTargetSkia
;
3643 if (pathDT
->Init(intBounds
.Size(), color
|| aShadow
3645 : SurfaceFormat::B8G8R8A8
)) {
3646 Point offset
= -quantBounds
.TopLeft();
3648 // Ensure the the shadow is drawn at the requested offset
3649 offset
+= aShadow
->mOffset
;
3651 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
,
3652 aOptions
.mAntialiasMode
);
3653 static const ColorPattern
maskPattern(DeviceColor(1.0f
, 1.0f
, 1.0f
, 1.0f
));
3654 const Pattern
& cachePattern
= color
? maskPattern
: aPattern
;
3655 // If the source pattern is a DrawTargetWebgl snapshot, we may shift
3656 // targets when drawing the path, so back up the old target.
3657 DrawTargetWebgl
* oldTarget
= mCurrentTarget
;
3659 RefPtr
<const Path
> path
;
3660 if (!aPathXform
|| (color
&& !aStrokeOptions
)) {
3661 // If the pattern is transform invariant or there is no pathXform, then
3662 // it is safe to use the path directly. Solid colors are transform
3663 // invariant, except when there are stroke options such as line width or
3664 // dashes that should not be scaled by pathXform.
3666 pathDT
->SetTransform(pathXform
* Matrix::Translation(offset
));
3668 // If there is a pathXform, then pre-apply that to the path to avoid
3669 // altering the pattern.
3670 RefPtr
<PathBuilder
> builder
=
3671 aPath
->TransformedCopyToBuilder(*aPathXform
);
3672 path
= builder
->Finish();
3673 pathDT
->SetTransform(currentTransform
* Matrix::Translation(offset
));
3675 if (aStrokeOptions
) {
3676 pathDT
->Stroke(path
, cachePattern
, *aStrokeOptions
, drawOptions
);
3678 pathDT
->Fill(path
, cachePattern
, drawOptions
);
3681 if (aShadow
&& aShadow
->mSigma
> 0.0f
) {
3682 // Blur the shadow if required.
3683 uint8_t* data
= nullptr;
3686 SurfaceFormat format
= SurfaceFormat::UNKNOWN
;
3687 if (pathDT
->LockBits(&data
, &size
, &stride
, &format
)) {
3688 AlphaBoxBlur
blur(Rect(pathDT
->GetRect()), stride
, aShadow
->mSigma
,
3691 pathDT
->ReleaseBits(data
);
3694 RefPtr
<SourceSurface
> pathSurface
= pathDT
->Snapshot();
3696 // If the target changed, try to restore it.
3697 if (mCurrentTarget
!= oldTarget
&& !oldTarget
->PrepareContext()) {
3700 SurfacePattern
pathPattern(pathSurface
, ExtendMode::CLAMP
,
3701 Matrix::Translation(quantBounds
.TopLeft()),
3703 // Try and upload the rasterized path to a texture. If there is a
3704 // valid texture handle after this, then link it to the entry.
3705 // Otherwise, we might have to fall back to software drawing the
3706 // path, so unlink it from the entry.
3707 if (DrawRectAccel(quantBounds
, pathPattern
, aOptions
, shadowColor
,
3708 &handle
, false, true) &&
3711 entry
->Link(handle
);
3723 void DrawTargetWebgl::DrawPath(const Path
* aPath
, const Pattern
& aPattern
,
3724 const DrawOptions
& aOptions
,
3725 const StrokeOptions
* aStrokeOptions
,
3726 bool aAllowStrokeAlpha
) {
3727 // If there is a WebGL context, then try to cache the path to avoid slow
3729 if (ShouldAccelPath(aOptions
, aStrokeOptions
) &&
3730 mSharedContext
->DrawPathAccel(aPath
, aPattern
, aOptions
, aStrokeOptions
,
3731 aAllowStrokeAlpha
)) {
3735 // There was no path cache entry available to use, so fall back to drawing the
3737 MarkSkiaChanged(aOptions
);
3738 if (aStrokeOptions
) {
3739 mSkia
->Stroke(aPath
, aPattern
, *aStrokeOptions
, aOptions
);
3741 mSkia
->Fill(aPath
, aPattern
, aOptions
);
3745 // DrawCircleAccel is a more specialized version of DrawPathAccel that attempts
3746 // to cache a unit circle.
3747 bool SharedContextWebgl::DrawCircleAccel(const Point
& aCenter
, float aRadius
,
3748 const Pattern
& aPattern
,
3749 const DrawOptions
& aOptions
,
3750 const StrokeOptions
* aStrokeOptions
) {
3751 // Cache a unit circle and transform it to avoid creating a path repeatedly.
3752 if (!mUnitCirclePath
) {
3753 mUnitCirclePath
= MakePathForCircle(*mCurrentTarget
, Point(0, 0), 1);
3755 // Scale and translate the circle to the desired shape.
3756 Matrix
circleXform(aRadius
, 0, 0, aRadius
, aCenter
.x
, aCenter
.y
);
3757 return DrawPathAccel(mUnitCirclePath
, aPattern
, aOptions
, aStrokeOptions
,
3758 true, nullptr, true, &circleXform
);
3761 void DrawTargetWebgl::DrawCircle(const Point
& aOrigin
, float aRadius
,
3762 const Pattern
& aPattern
,
3763 const DrawOptions
& aOptions
,
3764 const StrokeOptions
* aStrokeOptions
) {
3765 if (ShouldAccelPath(aOptions
, aStrokeOptions
) &&
3766 mSharedContext
->DrawCircleAccel(aOrigin
, aRadius
, aPattern
, aOptions
,
3771 MarkSkiaChanged(aOptions
);
3772 if (aStrokeOptions
) {
3773 mSkia
->StrokeCircle(aOrigin
, aRadius
, aPattern
, *aStrokeOptions
, aOptions
);
3775 mSkia
->FillCircle(aOrigin
, aRadius
, aPattern
, aOptions
);
3779 void DrawTargetWebgl::DrawSurface(SourceSurface
* aSurface
, const Rect
& aDest
,
3780 const Rect
& aSource
,
3781 const DrawSurfaceOptions
& aSurfOptions
,
3782 const DrawOptions
& aOptions
) {
3783 Matrix matrix
= Matrix::Scaling(aDest
.width
/ aSource
.width
,
3784 aDest
.height
/ aSource
.height
);
3785 matrix
.PreTranslate(-aSource
.x
, -aSource
.y
);
3786 matrix
.PostTranslate(aDest
.x
, aDest
.y
);
3787 SurfacePattern
pattern(aSurface
, ExtendMode::CLAMP
, matrix
,
3788 aSurfOptions
.mSamplingFilter
);
3789 DrawRect(aDest
, pattern
, aOptions
);
3792 void DrawTargetWebgl::Mask(const Pattern
& aSource
, const Pattern
& aMask
,
3793 const DrawOptions
& aOptions
) {
3794 if (!SupportsDrawOptions(aOptions
) ||
3795 aMask
.GetType() != PatternType::SURFACE
||
3796 aSource
.GetType() != PatternType::COLOR
) {
3797 MarkSkiaChanged(aOptions
);
3798 mSkia
->Mask(aSource
, aMask
, aOptions
);
3801 auto sourceColor
= static_cast<const ColorPattern
&>(aSource
).mColor
;
3802 auto maskPattern
= static_cast<const SurfacePattern
&>(aMask
);
3803 DrawRect(Rect(IntRect(IntPoint(), maskPattern
.mSurface
->GetSize())),
3804 maskPattern
, aOptions
, Some(sourceColor
));
3807 void DrawTargetWebgl::MaskSurface(const Pattern
& aSource
, SourceSurface
* aMask
,
3808 Point aOffset
, const DrawOptions
& aOptions
) {
3809 if (!SupportsDrawOptions(aOptions
) ||
3810 aSource
.GetType() != PatternType::COLOR
) {
3811 MarkSkiaChanged(aOptions
);
3812 mSkia
->MaskSurface(aSource
, aMask
, aOffset
, aOptions
);
3814 auto sourceColor
= static_cast<const ColorPattern
&>(aSource
).mColor
;
3815 SurfacePattern
pattern(aMask
, ExtendMode::CLAMP
,
3816 Matrix::Translation(aOffset
));
3817 DrawRect(Rect(aOffset
, Size(aMask
->GetSize())), pattern
, aOptions
,
3822 // Extract the surface's alpha values into an A8 surface.
3823 static already_AddRefed
<DataSourceSurface
> ExtractAlpha(SourceSurface
* aSurface
,
3824 bool aAllowSubpixelAA
) {
3825 RefPtr
<DataSourceSurface
> surfaceData
= aSurface
->GetDataSurface();
3829 DataSourceSurface::ScopedMap
srcMap(surfaceData
, DataSourceSurface::READ
);
3830 if (!srcMap
.IsMapped()) {
3833 IntSize size
= surfaceData
->GetSize();
3834 RefPtr
<DataSourceSurface
> alpha
=
3835 Factory::CreateDataSourceSurface(size
, SurfaceFormat::A8
, false);
3839 DataSourceSurface::ScopedMap
dstMap(alpha
, DataSourceSurface::WRITE
);
3840 if (!dstMap
.IsMapped()) {
3843 // For subpixel masks, ignore the alpha and instead sample one of the color
3844 // channels as if they were alpha.
3846 srcMap
.GetData(), srcMap
.GetStride(),
3847 aAllowSubpixelAA
? SurfaceFormat::A8R8G8B8
: surfaceData
->GetFormat(),
3848 dstMap
.GetData(), dstMap
.GetStride(), SurfaceFormat::A8
, size
);
3849 return alpha
.forget();
3852 void DrawTargetWebgl::DrawShadow(const Path
* aPath
, const Pattern
& aPattern
,
3853 const ShadowOptions
& aShadow
,
3854 const DrawOptions
& aOptions
,
3855 const StrokeOptions
* aStrokeOptions
) {
3856 if (!aPath
|| aPath
->GetBackendType() != BackendType::SKIA
) {
3860 // If there is a WebGL context, then try to cache the path to avoid slow
3862 if (ShouldAccelPath(aOptions
, aStrokeOptions
) &&
3863 mSharedContext
->DrawPathAccel(aPath
, aPattern
, aOptions
, aStrokeOptions
,
3868 // There was no path cache entry available to use, so fall back to drawing the
3870 MarkSkiaChanged(aOptions
);
3871 mSkia
->DrawShadow(aPath
, aPattern
, aShadow
, aOptions
, aStrokeOptions
);
3874 void DrawTargetWebgl::DrawSurfaceWithShadow(SourceSurface
* aSurface
,
3876 const ShadowOptions
& aShadow
,
3877 CompositionOp aOperator
) {
3878 DrawOptions
options(1.0f
, aOperator
);
3879 if (ShouldAccelPath(options
, nullptr)) {
3880 SurfacePattern
pattern(aSurface
, ExtendMode::CLAMP
,
3881 Matrix::Translation(aDest
));
3883 skiaPath
.addRect(RectToSkRect(Rect(aSurface
->GetRect()) + aDest
));
3884 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
3885 AutoRestoreTransform
restore(this);
3886 SetTransform(Matrix());
3887 if (mSharedContext
->DrawPathAccel(path
, pattern
, options
, nullptr, false,
3889 DrawRect(Rect(aSurface
->GetRect()) + aDest
, pattern
, options
);
3894 MarkSkiaChanged(options
);
3895 mSkia
->DrawSurfaceWithShadow(aSurface
, aDest
, aShadow
, aOperator
);
3898 already_AddRefed
<PathBuilder
> DrawTargetWebgl::CreatePathBuilder(
3899 FillRule aFillRule
) const {
3900 return mSkia
->CreatePathBuilder(aFillRule
);
3903 void DrawTargetWebgl::SetTransform(const Matrix
& aTransform
) {
3904 DrawTarget::SetTransform(aTransform
);
3905 mSkia
->SetTransform(aTransform
);
3908 void DrawTargetWebgl::StrokeRect(const Rect
& aRect
, const Pattern
& aPattern
,
3909 const StrokeOptions
& aStrokeOptions
,
3910 const DrawOptions
& aOptions
) {
3912 MarkSkiaChanged(aOptions
);
3913 mSkia
->StrokeRect(aRect
, aPattern
, aStrokeOptions
, aOptions
);
3915 // If the stroke options are unsupported, then transform the rect to a path
3916 // so it can be cached.
3918 skiaPath
.addRect(RectToSkRect(aRect
));
3919 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
3920 DrawPath(path
, aPattern
, aOptions
, &aStrokeOptions
, true);
3924 static inline bool IsThinLine(const Matrix
& aTransform
,
3925 const StrokeOptions
& aStrokeOptions
) {
3926 auto scale
= aTransform
.ScaleFactors();
3927 return std::max(scale
.xScale
, scale
.yScale
) * aStrokeOptions
.mLineWidth
<= 1;
3930 bool DrawTargetWebgl::StrokeLineAccel(const Point
& aStart
, const Point
& aEnd
,
3931 const Pattern
& aPattern
,
3932 const StrokeOptions
& aStrokeOptions
,
3933 const DrawOptions
& aOptions
,
3935 // Approximating a wide line as a rectangle works only with certain cap styles
3936 // in the general case (butt or square). However, if the line width is
3937 // sufficiently thin, we can either ignore the round cap (or treat it like
3938 // square for zero-length lines) without causing objectionable artifacts.
3939 // Lines may sometimes be used in closed paths that immediately reverse back,
3940 // in which case we need to use mLineJoin instead of mLineCap to determine the
3943 aClosed
? (aStrokeOptions
.mLineJoin
== JoinStyle::ROUND
? CapStyle::ROUND
3945 : aStrokeOptions
.mLineCap
;
3946 if (mWebglValid
&& SupportsPattern(aPattern
) &&
3947 (capStyle
!= CapStyle::ROUND
||
3948 IsThinLine(GetTransform(), aStrokeOptions
)) &&
3949 aStrokeOptions
.mDashPattern
== nullptr && aStrokeOptions
.mLineWidth
> 0) {
3950 // Treat the line as a rectangle whose center-line is the supplied line and
3951 // for which the height is the supplied line width. Generate a matrix that
3952 // maps the X axis to the orientation of the line and the Y axis to the
3953 // normal vector to the line. This only works if the line caps are squared,
3954 // as rounded rectangles are currently not supported for round line caps.
3955 Point start
= aStart
;
3956 Point dirX
= aEnd
- aStart
;
3958 float dirLen
= dirX
.Length();
3959 float scale
= aStrokeOptions
.mLineWidth
;
3960 if (dirLen
== 0.0f
) {
3961 // If the line is zero-length, then only a cap is rendered.
3963 case CapStyle::BUTT
:
3964 // The cap doesn't extend beyond the line so nothing is drawn.
3966 case CapStyle::ROUND
:
3967 case CapStyle::SQUARE
:
3968 // Draw a unit square centered at the single point.
3969 dirX
= Point(scale
, 0.0f
);
3970 dirY
= Point(0.0f
, scale
);
3971 // Offset the start by half a unit.
3972 start
.x
-= 0.5f
* scale
;
3976 // Make the scale map to a single unit length.
3978 dirY
= Point(-dirX
.y
, dirX
.x
) * scale
;
3979 if (capStyle
== CapStyle::SQUARE
) {
3980 // Offset the start by half a unit.
3981 start
-= (dirX
* scale
) * 0.5f
;
3982 // Ensure the extent also accounts for the start and end cap.
3983 dirX
+= dirX
* scale
;
3986 Matrix
lineXform(dirX
.x
, dirX
.y
, dirY
.x
, dirY
.y
, start
.x
- 0.5f
* dirY
.x
,
3987 start
.y
- 0.5f
* dirY
.y
);
3988 if (PrepareContext() &&
3989 mSharedContext
->DrawRectAccel(Rect(0, 0, 1, 1), aPattern
, aOptions
,
3990 Nothing(), nullptr, true, true, true,
3991 false, nullptr, nullptr, &lineXform
)) {
3998 void DrawTargetWebgl::StrokeLine(const Point
& aStart
, const Point
& aEnd
,
3999 const Pattern
& aPattern
,
4000 const StrokeOptions
& aStrokeOptions
,
4001 const DrawOptions
& aOptions
) {
4003 MarkSkiaChanged(aOptions
);
4004 mSkia
->StrokeLine(aStart
, aEnd
, aPattern
, aStrokeOptions
, aOptions
);
4005 } else if (!StrokeLineAccel(aStart
, aEnd
, aPattern
, aStrokeOptions
,
4007 // If the stroke options are unsupported, then transform the line to a path
4008 // so it can be cached.
4010 skiaPath
.moveTo(PointToSkPoint(aStart
));
4011 skiaPath
.lineTo(PointToSkPoint(aEnd
));
4012 RefPtr
<PathSkia
> path
= new PathSkia(skiaPath
, FillRule::FILL_WINDING
);
4013 DrawPath(path
, aPattern
, aOptions
, &aStrokeOptions
, true);
4017 void DrawTargetWebgl::Stroke(const Path
* aPath
, const Pattern
& aPattern
,
4018 const StrokeOptions
& aStrokeOptions
,
4019 const DrawOptions
& aOptions
) {
4020 if (!aPath
|| aPath
->GetBackendType() != BackendType::SKIA
) {
4023 const auto& skiaPath
= static_cast<const PathSkia
*>(aPath
)->GetPath();
4025 MarkSkiaChanged(aOptions
);
4026 mSkia
->Stroke(aPath
, aPattern
, aStrokeOptions
, aOptions
);
4030 // Avoid using Skia's isLine here because some paths erroneously include a
4031 // closePath at the end, causing isLine to not detect the line. In that case
4032 // we just draw a line in reverse right over the original line.
4033 int numVerbs
= skiaPath
.countVerbs();
4034 bool allowStrokeAlpha
= false;
4035 if (numVerbs
>= 2 && numVerbs
<= 3) {
4037 skiaPath
.getVerbs(verbs
, numVerbs
);
4038 if (verbs
[0] == SkPath::kMove_Verb
&& verbs
[1] == SkPath::kLine_Verb
&&
4039 (numVerbs
< 3 || verbs
[2] == SkPath::kClose_Verb
)) {
4040 bool closed
= numVerbs
>= 3;
4041 Point start
= SkPointToPoint(skiaPath
.getPoint(0));
4042 Point end
= SkPointToPoint(skiaPath
.getPoint(1));
4043 if (StrokeLineAccel(start
, end
, aPattern
, aStrokeOptions
, aOptions
,
4046 StrokeLineAccel(end
, start
, aPattern
, aStrokeOptions
, aOptions
, true);
4050 // If accelerated line drawing failed, just treat it as a path.
4051 allowStrokeAlpha
= true;
4055 DrawPath(aPath
, aPattern
, aOptions
, &aStrokeOptions
, allowStrokeAlpha
);
4058 void DrawTargetWebgl::StrokeCircle(const Point
& aOrigin
, float aRadius
,
4059 const Pattern
& aPattern
,
4060 const StrokeOptions
& aStrokeOptions
,
4061 const DrawOptions
& aOptions
) {
4062 DrawCircle(aOrigin
, aRadius
, aPattern
, aOptions
, &aStrokeOptions
);
4065 bool DrawTargetWebgl::ShouldUseSubpixelAA(ScaledFont
* aFont
,
4066 const DrawOptions
& aOptions
) {
4067 AntialiasMode aaMode
= aFont
->GetDefaultAAMode();
4068 if (aOptions
.mAntialiasMode
!= AntialiasMode::DEFAULT
) {
4069 aaMode
= aOptions
.mAntialiasMode
;
4071 return GetPermitSubpixelAA() &&
4072 (aaMode
== AntialiasMode::DEFAULT
||
4073 aaMode
== AntialiasMode::SUBPIXEL
) &&
4074 aOptions
.mCompositionOp
== CompositionOp::OP_OVER
;
4077 void DrawTargetWebgl::StrokeGlyphs(ScaledFont
* aFont
,
4078 const GlyphBuffer
& aBuffer
,
4079 const Pattern
& aPattern
,
4080 const StrokeOptions
& aStrokeOptions
,
4081 const DrawOptions
& aOptions
) {
4082 if (!aFont
|| !aBuffer
.mNumGlyphs
) {
4086 bool useSubpixelAA
= ShouldUseSubpixelAA(aFont
, aOptions
);
4088 if (mWebglValid
&& SupportsDrawOptions(aOptions
) &&
4089 aPattern
.GetType() == PatternType::COLOR
&& PrepareContext() &&
4090 mSharedContext
->DrawGlyphsAccel(aFont
, aBuffer
, aPattern
, aOptions
,
4091 &aStrokeOptions
, useSubpixelAA
)) {
4095 if (useSubpixelAA
) {
4096 // Subpixel AA does not support layering because the subpixel masks can't
4097 // blend with the over op.
4100 MarkSkiaChanged(aOptions
);
4102 mSkia
->StrokeGlyphs(aFont
, aBuffer
, aPattern
, aStrokeOptions
, aOptions
);
4105 // Depending on whether we enable subpixel position for a given font, Skia may
4106 // round transformed coordinates differently on each axis. By default, text is
4107 // subpixel quantized horizontally and snapped to a whole integer vertical
4108 // baseline. Axis-flip transforms instead snap to horizontal boundaries while
4109 // subpixel quantizing along the vertical. For other types of transforms, Skia
4110 // just applies subpixel quantization to both axes.
4111 // We must duplicate the amount of quantization Skia applies carefully as a
4112 // boundary value such as 0.49 may round to 0.5 with subpixel quantization,
4113 // but if Skia actually snapped it to a whole integer instead, it would round
4114 // down to 0. If a subsequent glyph with offset 0.51 came in, we might
4115 // mistakenly round it down to 0.5, whereas Skia would round it up to 1. Thus
4116 // we would alias 0.49 and 0.51 to the same cache entry, while Skia would
4117 // actually snap the offset to 0 or 1, depending, resulting in mismatched
4119 static inline IntPoint
QuantizeScale(ScaledFont
* aFont
,
4120 const Matrix
& aTransform
) {
4121 if (!aFont
->UseSubpixelPosition()) {
4124 if (aTransform
._12
== 0) {
4125 // Glyphs are rendered subpixel horizontally, so snap vertically.
4128 if (aTransform
._11
== 0) {
4129 // Glyphs are rendered subpixel vertically, so snap horizontally.
4132 // The transform isn't aligned, so don't snap.
4136 // Skia only supports subpixel positioning to the nearest 1/4 fraction. It
4137 // would be wasteful to attempt to cache text runs with positioning that is
4138 // anymore precise than this. To prevent this cache bloat, we quantize the
4139 // transformed glyph positions to the nearest 1/4. The scaling factor for
4140 // the quantization is baked into the transform, so that if subpixel rounding
4141 // is used on a given axis, then the axis will be multiplied by 4 before
4142 // rounding. Since the quantized position is not used for rasterization, the
4143 // transform is safe to modify as such.
4144 static inline IntPoint
QuantizePosition(const Matrix
& aTransform
,
4145 const IntPoint
& aOffset
,
4146 const Point
& aPosition
) {
4147 return RoundedToInt(aTransform
.TransformPoint(aPosition
)) - aOffset
;
4150 // Get a quantized starting offset for the glyph buffer. We want this offset
4151 // to encapsulate the transform and buffer offset while still preserving the
4152 // relative subpixel positions of the glyphs this offset is subtracted from.
4153 static inline IntPoint
QuantizeOffset(const Matrix
& aTransform
,
4154 const IntPoint
& aQuantizeScale
,
4155 const GlyphBuffer
& aBuffer
) {
4157 RoundedToInt(aTransform
.TransformPoint(aBuffer
.mGlyphs
[0].mPosition
));
4158 offset
.x
.value
&= ~(aQuantizeScale
.x
.value
- 1);
4159 offset
.y
.value
&= ~(aQuantizeScale
.y
.value
- 1);
4163 // Hashes a glyph buffer to a single hash value that can be used for quick
4164 // comparisons. Each glyph position is transformed and quantized before
4166 HashNumber
GlyphCacheEntry::HashGlyphs(const GlyphBuffer
& aBuffer
,
4167 const Matrix
& aTransform
,
4168 const IntPoint
& aQuantizeScale
) {
4169 HashNumber hash
= 0;
4170 IntPoint offset
= QuantizeOffset(aTransform
, aQuantizeScale
, aBuffer
);
4171 for (size_t i
= 0; i
< aBuffer
.mNumGlyphs
; i
++) {
4172 const Glyph
& glyph
= aBuffer
.mGlyphs
[i
];
4173 hash
= AddToHash(hash
, glyph
.mIndex
);
4174 IntPoint pos
= QuantizePosition(aTransform
, offset
, glyph
.mPosition
);
4175 hash
= AddToHash(hash
, pos
.x
);
4176 hash
= AddToHash(hash
, pos
.y
);
4181 // Determines if an existing glyph cache entry matches an incoming text run.
4182 inline bool GlyphCacheEntry::MatchesGlyphs(
4183 const GlyphBuffer
& aBuffer
, const DeviceColor
& aColor
,
4184 const Matrix
& aTransform
, const IntPoint
& aQuantizeOffset
,
4185 const IntPoint
& aBoundsOffset
, const IntRect
& aClipRect
, HashNumber aHash
,
4186 const StrokeOptions
* aStrokeOptions
) {
4187 // First check if the hash matches to quickly reject the text run before any
4188 // more expensive checking. If it matches, then check if the color and
4189 // transform are the same.
4190 if (aHash
!= mHash
|| aBuffer
.mNumGlyphs
!= mBuffer
.mNumGlyphs
||
4191 aColor
!= mColor
|| !HasMatchingScale(aTransform
, mTransform
)) {
4194 // Finally check if all glyphs and their quantized positions match.
4195 for (size_t i
= 0; i
< aBuffer
.mNumGlyphs
; i
++) {
4196 const Glyph
& dst
= mBuffer
.mGlyphs
[i
];
4197 const Glyph
& src
= aBuffer
.mGlyphs
[i
];
4198 if (dst
.mIndex
!= src
.mIndex
||
4199 dst
.mPosition
!= Point(QuantizePosition(aTransform
, aQuantizeOffset
,
4204 // Check that stroke options actually match.
4205 if (aStrokeOptions
) {
4206 // If stroking, verify that the entry is also stroked with the same options.
4207 if (!(mStrokeOptions
&& *aStrokeOptions
== *mStrokeOptions
)) {
4210 } else if (mStrokeOptions
) {
4211 // If not stroking, check if the entry is stroked. If so, don't match.
4214 // Verify that the full bounds, once translated and clipped, are equal to the
4216 return (mFullBounds
+ aBoundsOffset
)
4217 .Intersect(aClipRect
)
4218 .IsEqualEdges(GetBounds() + aBoundsOffset
);
4221 GlyphCacheEntry::GlyphCacheEntry(const GlyphBuffer
& aBuffer
,
4222 const DeviceColor
& aColor
,
4223 const Matrix
& aTransform
,
4224 const IntPoint
& aQuantizeScale
,
4225 const IntRect
& aBounds
,
4226 const IntRect
& aFullBounds
, HashNumber aHash
,
4227 StoredStrokeOptions
* aStrokeOptions
)
4228 : CacheEntryImpl
<GlyphCacheEntry
>(aTransform
, aBounds
, aHash
),
4230 mFullBounds(aFullBounds
),
4231 mStrokeOptions(aStrokeOptions
) {
4232 // Store a copy of the glyph buffer with positions already quantized for fast
4233 // comparison later.
4234 Glyph
* glyphs
= new Glyph
[aBuffer
.mNumGlyphs
];
4235 IntPoint offset
= QuantizeOffset(aTransform
, aQuantizeScale
, aBuffer
);
4236 // Make the bounds relative to the offset so we can add a new offset later.
4237 IntPoint
boundsOffset(offset
.x
/ aQuantizeScale
.x
,
4238 offset
.y
/ aQuantizeScale
.y
);
4239 mBounds
-= boundsOffset
;
4240 mFullBounds
-= boundsOffset
;
4241 for (size_t i
= 0; i
< aBuffer
.mNumGlyphs
; i
++) {
4242 Glyph
& dst
= glyphs
[i
];
4243 const Glyph
& src
= aBuffer
.mGlyphs
[i
];
4244 dst
.mIndex
= src
.mIndex
;
4245 dst
.mPosition
= Point(QuantizePosition(aTransform
, offset
, src
.mPosition
));
4247 mBuffer
.mGlyphs
= glyphs
;
4248 mBuffer
.mNumGlyphs
= aBuffer
.mNumGlyphs
;
4251 GlyphCacheEntry::~GlyphCacheEntry() { delete[] mBuffer
.mGlyphs
; }
4253 // Attempt to find a matching entry in the glyph cache. The caller should check
4254 // whether the contained texture handle is valid to determine if it will need to
4255 // render the text run or just reuse the cached texture.
4256 already_AddRefed
<GlyphCacheEntry
> GlyphCache::FindEntry(
4257 const GlyphBuffer
& aBuffer
, const DeviceColor
& aColor
,
4258 const Matrix
& aTransform
, const IntPoint
& aQuantizeScale
,
4259 const IntRect
& aClipRect
, HashNumber aHash
,
4260 const StrokeOptions
* aStrokeOptions
) {
4261 IntPoint offset
= QuantizeOffset(aTransform
, aQuantizeScale
, aBuffer
);
4262 IntPoint
boundsOffset(offset
.x
/ aQuantizeScale
.x
,
4263 offset
.y
/ aQuantizeScale
.y
);
4264 for (const RefPtr
<GlyphCacheEntry
>& entry
: GetChain(aHash
)) {
4265 if (entry
->MatchesGlyphs(aBuffer
, aColor
, aTransform
, offset
, boundsOffset
,
4266 aClipRect
, aHash
, aStrokeOptions
)) {
4267 return do_AddRef(entry
);
4273 // Insert a new entry in the glyph cache.
4274 already_AddRefed
<GlyphCacheEntry
> GlyphCache::InsertEntry(
4275 const GlyphBuffer
& aBuffer
, const DeviceColor
& aColor
,
4276 const Matrix
& aTransform
, const IntPoint
& aQuantizeScale
,
4277 const IntRect
& aBounds
, const IntRect
& aFullBounds
, HashNumber aHash
,
4278 const StrokeOptions
* aStrokeOptions
) {
4279 StoredStrokeOptions
* strokeOptions
= nullptr;
4280 if (aStrokeOptions
) {
4281 strokeOptions
= aStrokeOptions
->Clone();
4282 if (!strokeOptions
) {
4286 RefPtr
<GlyphCacheEntry
> entry
=
4287 new GlyphCacheEntry(aBuffer
, aColor
, aTransform
, aQuantizeScale
, aBounds
,
4288 aFullBounds
, aHash
, strokeOptions
);
4290 return entry
.forget();
4293 GlyphCache::GlyphCache(ScaledFont
* aFont
) : mFont(aFont
) {}
4295 static void ReleaseGlyphCache(void* aPtr
) {
4296 delete static_cast<GlyphCache
*>(aPtr
);
4299 // Whether all glyphs in the buffer match the last whitespace glyph queried.
4300 bool GlyphCache::IsWhitespace(const GlyphBuffer
& aBuffer
) const {
4301 if (!mLastWhitespace
) {
4304 uint32_t whitespace
= *mLastWhitespace
;
4305 for (size_t i
= 0; i
< aBuffer
.mNumGlyphs
; ++i
) {
4306 if (aBuffer
.mGlyphs
[i
].mIndex
!= whitespace
) {
4313 // Remember the last whitespace glyph seen.
4314 void GlyphCache::SetLastWhitespace(const GlyphBuffer
& aBuffer
) {
4315 mLastWhitespace
= Some(aBuffer
.mGlyphs
[0].mIndex
);
4318 void DrawTargetWebgl::SetPermitSubpixelAA(bool aPermitSubpixelAA
) {
4319 DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA
);
4320 mSkia
->SetPermitSubpixelAA(aPermitSubpixelAA
);
4323 // Check for any color glyphs contained within a rasterized BGRA8 text result.
4324 static bool CheckForColorGlyphs(const RefPtr
<SourceSurface
>& aSurface
) {
4325 if (aSurface
->GetFormat() != SurfaceFormat::B8G8R8A8
) {
4328 RefPtr
<DataSourceSurface
> dataSurf
= aSurface
->GetDataSurface();
4332 DataSourceSurface::ScopedMap
map(dataSurf
, DataSourceSurface::READ
);
4333 if (!map
.IsMapped()) {
4336 IntSize size
= dataSurf
->GetSize();
4337 const uint8_t* data
= map
.GetData();
4338 int32_t stride
= map
.GetStride();
4339 for (int y
= 0; y
< size
.height
; y
++) {
4340 const uint32_t* x
= (const uint32_t*)data
;
4341 const uint32_t* end
= x
+ size
.width
;
4342 for (; x
< end
; x
++) {
4343 // Verify if all components are the same as for premultiplied grayscale.
4344 uint32_t color
= *x
;
4345 uint32_t gray
= color
& 0xFF;
4348 if (color
!= gray
) return true;
4355 // Quantize the preblend color used to key the cache, as only the high bits are
4356 // used to determine the amount of preblending. This avoids excessive cache use.
4357 // This roughly matches the quantization used in WebRender and Skia.
4358 static DeviceColor
QuantizePreblendColor(const DeviceColor
& aColor
,
4359 bool aUseSubpixelAA
) {
4360 int32_t r
= int32_t(aColor
.r
* 255.0f
+ 0.5f
);
4361 int32_t g
= int32_t(aColor
.g
* 255.0f
+ 0.5f
);
4362 int32_t b
= int32_t(aColor
.b
* 255.0f
+ 0.5f
);
4363 // Skia only uses the high 3 bits of each color component to cache preblend
4365 constexpr int32_t lumBits
= 3;
4366 constexpr int32_t floorMask
= ((1 << lumBits
) - 1) << (8 - lumBits
);
4367 if (!aUseSubpixelAA
) {
4368 // If not using subpixel AA, then quantize only the luminance, stored in the
4370 g
= (r
* 54 + g
* 183 + b
* 19) >> 8;
4379 return DeviceColor
{r
/ 255.0f
, g
/ 255.0f
, b
/ 255.0f
, 1.0f
};
4382 // Draws glyphs to the WebGL target by trying to generate a cached texture for
4383 // the text run that can be subsequently reused to quickly render the text run
4384 // without using any software surfaces.
4385 bool SharedContextWebgl::DrawGlyphsAccel(ScaledFont
* aFont
,
4386 const GlyphBuffer
& aBuffer
,
4387 const Pattern
& aPattern
,
4388 const DrawOptions
& aOptions
,
4389 const StrokeOptions
* aStrokeOptions
,
4390 bool aUseSubpixelAA
) {
4391 // Look for an existing glyph cache on the font. If not there, create it.
4393 static_cast<GlyphCache
*>(aFont
->GetUserData(&mGlyphCacheKey
));
4395 cache
= new GlyphCache(aFont
);
4396 aFont
->AddUserData(&mGlyphCacheKey
, cache
, ReleaseGlyphCache
);
4397 mGlyphCaches
.insertFront(cache
);
4400 // Check if the buffer contains non-renderable whitespace characters before
4401 // any other expensive checks.
4402 if (cache
->IsWhitespace(aBuffer
)) {
4406 // Whether the font may use bitmaps. If so, we need to render the glyphs with
4407 // color as grayscale bitmaps will use the color while color emoji will not,
4408 // with no easy way to know ahead of time. We currently have to check the
4409 // rasterized result to see if there are any color glyphs. To render subpixel
4410 // masks, we need to know that the rasterized result actually represents a
4411 // subpixel mask rather than try to interpret it as a normal RGBA result such
4412 // as for color emoji.
4413 bool useBitmaps
= !aStrokeOptions
&& aFont
->MayUseBitmaps() &&
4414 aOptions
.mCompositionOp
!= CompositionOp::OP_CLEAR
;
4415 // Hash the incoming text run and looking for a matching entry.
4416 DeviceColor color
= aOptions
.mCompositionOp
== CompositionOp::OP_CLEAR
4417 ? DeviceColor(1, 1, 1, 1)
4418 : static_cast<const ColorPattern
&>(aPattern
).mColor
;
4419 #if defined(XP_MACOSX)
4420 // macOS uses gamma-aware blending with font smooth from subpixel AA.
4421 // If font smoothing is requested, even if there is no subpixel AA, gamma-
4422 // aware blending might be used and differing amounts of dilation might be
4424 bool usePreblend
= aUseSubpixelAA
||
4425 (aFont
->GetType() == FontType::MAC
&&
4426 static_cast<ScaledFontMac
*>(aFont
)->UseFontSmoothing());
4427 #elif defined(XP_WIN)
4428 // Windows uses gamma-aware blending via ClearType with grayscale and subpixel
4431 aUseSubpixelAA
|| aOptions
.mAntialiasMode
!= AntialiasMode::NONE
;
4433 // FreeType backends currently don't use any preblending.
4434 bool usePreblend
= false;
4437 // If the font has bitmaps, use the color directly. Otherwise, the texture
4438 // holds a grayscale mask, so encode the key's subpixel state in the color.
4439 const Matrix
& currentTransform
= mCurrentTarget
->GetTransform();
4440 IntPoint quantizeScale
= QuantizeScale(aFont
, currentTransform
);
4441 Matrix quantizeTransform
= currentTransform
;
4442 quantizeTransform
.PostScale(quantizeScale
.x
, quantizeScale
.y
);
4444 GlyphCacheEntry::HashGlyphs(aBuffer
, quantizeTransform
, quantizeScale
);
4445 DeviceColor colorOrMask
=
4447 : (usePreblend
? QuantizePreblendColor(color
, aUseSubpixelAA
)
4448 : DeviceColor::Mask(aUseSubpixelAA
? 1 : 0, 1));
4449 IntRect
clipRect(IntPoint(), mViewportSize
);
4450 RefPtr
<GlyphCacheEntry
> entry
=
4451 cache
->FindEntry(aBuffer
, colorOrMask
, quantizeTransform
, quantizeScale
,
4452 clipRect
, hash
, aStrokeOptions
);
4454 // For small text runs, bounds computations can be expensive relative to the
4455 // cost of looking up a cache result. Avoid doing local bounds computations
4456 // until actually inserting the entry into the cache.
4457 Maybe
<Rect
> bounds
= mCurrentTarget
->mSkia
->GetGlyphLocalBounds(
4458 aFont
, aBuffer
, aPattern
, aStrokeOptions
, aOptions
);
4460 // Assume the buffer is full of whitespace characters that should be
4461 // remembered for subsequent lookups.
4462 cache
->SetLastWhitespace(aBuffer
);
4465 // Transform the local bounds into device space so that we know how big
4466 // the cached texture will be.
4467 Rect xformBounds
= currentTransform
.TransformBounds(*bounds
);
4468 // Check if the transform flattens out the bounds before rounding.
4469 if (xformBounds
.IsEmpty()) {
4472 IntRect fullBounds
= RoundedOut(xformBounds
);
4473 IntRect clipBounds
= fullBounds
.Intersect(clipRect
);
4474 // Check if the bounds are completely clipped out.
4475 if (clipBounds
.IsEmpty()) {
4478 entry
= cache
->InsertEntry(aBuffer
, colorOrMask
, quantizeTransform
,
4479 quantizeScale
, clipBounds
, fullBounds
, hash
,
4486 // The bounds of the entry may have a different transform offset from the
4487 // bounds of the currently drawn text run. The entry bounds are relative to
4488 // the entry's quantized offset already, so just move the bounds to the new
4490 IntRect intBounds
= entry
->GetBounds();
4491 IntPoint newOffset
=
4492 QuantizeOffset(quantizeTransform
, quantizeScale
, aBuffer
);
4494 IntPoint(newOffset
.x
/ quantizeScale
.x
, newOffset
.y
/ quantizeScale
.y
);
4495 // Ensure there is a clear border around the text. This must be applied only
4496 // after clipping so that we always have some border texels for filtering.
4497 intBounds
.Inflate(2);
4499 RefPtr
<TextureHandle
> handle
= entry
->GetHandle();
4500 if (handle
&& handle
->IsValid()) {
4501 // If there is an entry with a valid cached texture handle, then try
4502 // to draw with that. If that for some reason failed, then fall back
4503 // to using the Skia target as that means we were preventing from
4504 // drawing to the WebGL context based on something other than the
4506 SurfacePattern
pattern(nullptr, ExtendMode::CLAMP
,
4507 Matrix::Translation(intBounds
.TopLeft()));
4508 if (DrawRectAccel(Rect(intBounds
), pattern
, aOptions
,
4509 useBitmaps
? Nothing() : Some(color
), &handle
, false,
4516 // If we get here, either there wasn't a cached texture handle or it
4517 // wasn't valid. Render the text run into a temporary target.
4518 RefPtr
<DrawTargetSkia
> textDT
= new DrawTargetSkia
;
4519 if (textDT
->Init(intBounds
.Size(),
4520 useBitmaps
|| usePreblend
|| aUseSubpixelAA
4521 ? SurfaceFormat::B8G8R8A8
4522 : SurfaceFormat::A8
)) {
4523 textDT
->SetTransform(currentTransform
*
4524 Matrix::Translation(-intBounds
.TopLeft()));
4525 textDT
->SetPermitSubpixelAA(aUseSubpixelAA
);
4526 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
,
4527 aOptions
.mAntialiasMode
);
4528 // If bitmaps might be used, then we have to supply the color, as color
4529 // emoji may ignore it while grayscale bitmaps may use it, with no way to
4530 // know ahead of time. If we are using preblending in some form, then the
4531 // output also will depend on the supplied color. Otherwise, assume the
4532 // output will be a mask and just render it white to determine intensity.
4533 if (!useBitmaps
&& usePreblend
) {
4534 textDT
->DrawGlyphMask(aFont
, aBuffer
, color
, aStrokeOptions
,
4537 ColorPattern
colorPattern(useBitmaps
? color
: DeviceColor(1, 1, 1, 1));
4538 if (aStrokeOptions
) {
4539 textDT
->StrokeGlyphs(aFont
, aBuffer
, colorPattern
, *aStrokeOptions
,
4542 textDT
->FillGlyphs(aFont
, aBuffer
, colorPattern
, drawOptions
);
4545 RefPtr
<SourceSurface
> textSurface
= textDT
->Snapshot();
4547 // If we don't expect the text surface to contain color glyphs
4548 // such as from subpixel AA, then do one final check to see if
4549 // any ended up in the result. If not, extract the alpha values
4550 // from the surface so we can render it as a mask.
4551 if (textSurface
->GetFormat() != SurfaceFormat::A8
&&
4552 !CheckForColorGlyphs(textSurface
)) {
4553 textSurface
= ExtractAlpha(textSurface
, !useBitmaps
);
4555 // Failed extracting alpha for the text surface...
4559 // Attempt to upload the rendered text surface into a texture
4560 // handle and draw it.
4561 SurfacePattern
pattern(textSurface
, ExtendMode::CLAMP
,
4562 Matrix::Translation(intBounds
.TopLeft()));
4563 if (DrawRectAccel(Rect(intBounds
), pattern
, aOptions
,
4564 useBitmaps
? Nothing() : Some(color
), &handle
, false,
4567 // If drawing succeeded, then the text surface was uploaded to
4568 // a texture handle. Assign it to the glyph cache entry.
4569 entry
->Link(handle
);
4571 // If drawing failed, remove the entry from the cache.
4581 void DrawTargetWebgl::FillGlyphs(ScaledFont
* aFont
, const GlyphBuffer
& aBuffer
,
4582 const Pattern
& aPattern
,
4583 const DrawOptions
& aOptions
) {
4584 if (!aFont
|| !aBuffer
.mNumGlyphs
) {
4588 bool useSubpixelAA
= ShouldUseSubpixelAA(aFont
, aOptions
);
4590 if (mWebglValid
&& SupportsDrawOptions(aOptions
) &&
4591 aPattern
.GetType() == PatternType::COLOR
&& PrepareContext() &&
4592 mSharedContext
->DrawGlyphsAccel(aFont
, aBuffer
, aPattern
, aOptions
,
4593 nullptr, useSubpixelAA
)) {
4597 // If not able to cache the text run to a texture, then just fall back to
4598 // drawing with the Skia target.
4599 if (useSubpixelAA
) {
4600 // Subpixel AA does not support layering because the subpixel masks can't
4601 // blend with the over op.
4604 MarkSkiaChanged(aOptions
);
4606 mSkia
->FillGlyphs(aFont
, aBuffer
, aPattern
, aOptions
);
4609 // Attempts to read the contents of the WebGL context into the Skia target.
4610 bool DrawTargetWebgl::ReadIntoSkia() {
4614 bool didReadback
= false;
4616 uint8_t* data
= nullptr;
4619 SurfaceFormat format
;
4621 // If the WebGL target is still clear, then just clear the Skia target.
4622 mSkia
->DetachAllSnapshots();
4623 mSkiaNoClip
->FillRect(Rect(mSkiaNoClip
->GetRect()), GetClearPattern(),
4624 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
4626 // If there's no existing snapshot and we can successfully map the Skia
4627 // target for reading, then try to read into that.
4628 if (!mSnapshot
&& mSkia
->LockBits(&data
, &size
, &stride
, &format
)) {
4629 (void)ReadInto(data
, stride
);
4630 mSkia
->ReleaseBits(data
);
4631 } else if (RefPtr
<SourceSurface
> snapshot
= Snapshot()) {
4632 // Otherwise, fall back to getting a snapshot from WebGL if available
4633 // and then copying that to Skia.
4634 mSkia
->CopySurface(snapshot
, GetRect(), IntPoint(0, 0));
4640 // The Skia data is flat after reading, so disable any layering.
4645 // Reads data from the WebGL context and blends it with the current Skia layer.
4646 void DrawTargetWebgl::FlattenSkia() {
4647 if (!mSkiaValid
|| !mSkiaLayer
) {
4651 if (mSkiaLayerClear
) {
4652 // If the WebGL target is clear, then there is nothing to blend.
4655 if (RefPtr
<DataSourceSurface
> base
= ReadSnapshot()) {
4656 mSkia
->DetachAllSnapshots();
4657 mSkiaNoClip
->DrawSurface(base
, Rect(GetRect()), Rect(GetRect()),
4658 DrawSurfaceOptions(SamplingFilter::POINT
),
4659 DrawOptions(1.f
, CompositionOp::OP_DEST_OVER
));
4663 // Attempts to draw the contents of the Skia target into the WebGL context.
4664 bool DrawTargetWebgl::FlushFromSkia() {
4665 // If the WebGL context has been lost, then mark it as invalid and fail.
4666 if (mSharedContext
->IsContextLost()) {
4667 mWebglValid
= false;
4670 // The WebGL target is already valid, so there is nothing to do.
4674 // Ensure that DrawRect doesn't recursively call into FlushFromSkia. If
4675 // the Skia target isn't valid, then it doesn't matter what is in the the
4676 // WebGL target either, so only try to blend if there is a valid Skia target.
4679 AutoRestoreContext
restore(this);
4681 // If the Skia target is clear, then there is no need to use a snapshot.
4682 // Directly clear the WebGL target instead.
4684 if (!DrawRect(Rect(GetRect()), GetClearPattern(),
4685 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
), Nothing(),
4686 nullptr, false, false, true)) {
4687 mWebglValid
= false;
4693 RefPtr
<SourceSurface
> skiaSnapshot
= mSkia
->Snapshot();
4694 if (!skiaSnapshot
) {
4695 // There's a valid Skia target to draw to, but for some reason there is
4696 // no available snapshot, so just keep using the Skia target.
4697 mWebglValid
= false;
4701 // If there is no layer, then just upload it directly.
4703 if (PrepareContext(false) && MarkChanged()) {
4704 if (RefPtr
<DataSourceSurface
> data
= skiaSnapshot
->GetDataSurface()) {
4705 mSharedContext
->UploadSurface(data
, mFormat
, GetRect(), IntPoint(),
4706 false, false, mTex
);
4710 // Failed to upload the Skia snapshot.
4711 mWebglValid
= false;
4715 SurfacePattern
pattern(skiaSnapshot
, ExtendMode::CLAMP
);
4716 // If there is a layer, blend the snapshot with the WebGL context.
4717 if (!DrawRect(Rect(GetRect()), pattern
,
4718 DrawOptions(1.0f
, CompositionOp::OP_OVER
), Nothing(),
4719 &mSnapshotTexture
, false, false, true, true)) {
4720 // If accelerated drawing failed for some reason, then leave the Skia
4721 // target unchanged.
4722 mWebglValid
= false;
4729 void DrawTargetWebgl::UsageProfile::BeginFrame() {
4730 // Reset the usage profile counters for the new frame.
4739 void DrawTargetWebgl::UsageProfile::EndFrame() {
4740 bool failed
= false;
4741 // If we hit a complete fallback to software rendering, or if cache misses
4742 // were more than cutoff ratio of all requests, then we consider the frame as
4743 // having failed performance profiling.
4745 StaticPrefs::gfx_canvas_accelerated_profile_cache_miss_ratio();
4746 if (mFallbacks
> 0 ||
4747 float(mCacheMisses
+ mReadbacks
+ mLayers
) >
4748 cacheRatio
* float(mCacheMisses
+ mCacheHits
+ mUncachedDraws
+
4749 mReadbacks
+ mLayers
)) {
4758 bool DrawTargetWebgl::UsageProfile::RequiresRefresh() const {
4759 // If we've rendered at least the required number of frames for a profile and
4760 // more than the cutoff ratio of frames did not meet performance criteria,
4761 // then we should stop using an accelerated canvas.
4762 uint32_t profileFrames
= StaticPrefs::gfx_canvas_accelerated_profile_frames();
4763 if (!profileFrames
|| mFrameCount
< profileFrames
) {
4767 StaticPrefs::gfx_canvas_accelerated_profile_fallback_ratio();
4768 return mFailedFrames
> failRatio
* mFrameCount
;
4771 void SharedContextWebgl::CachePrefs() {
4772 uint32_t capacity
= StaticPrefs::gfx_canvas_accelerated_gpu_path_size() << 20;
4773 if (capacity
!= mPathVertexCapacity
) {
4774 mPathVertexCapacity
= capacity
;
4776 mPathCache
->ClearVertexRanges();
4778 if (mPathVertexBuffer
) {
4779 ResetPathVertexBuffer();
4783 mPathMaxComplexity
=
4784 StaticPrefs::gfx_canvas_accelerated_gpu_path_complexity();
4786 mPathAAStroke
= StaticPrefs::gfx_canvas_accelerated_aa_stroke_enabled();
4787 mPathWGRStroke
= StaticPrefs::gfx_canvas_accelerated_stroke_to_fill_path();
4790 // For use within CanvasRenderingContext2D, called on BorrowDrawTarget.
4791 void DrawTargetWebgl::BeginFrame(bool aInvalidContents
) {
4792 // If still rendering into the Skia target, switch back to the WebGL
4795 if (aInvalidContents
) {
4796 // If nothing needs to persist, just mark the WebGL context valid.
4798 // Even if the Skia framebuffer is marked clear, since the WebGL
4799 // context is not valid, its contents may be out-of-date and not
4800 // necessarily clear.
4806 // Check if we need to clear out any cached because of memory pressure.
4807 mSharedContext
->ClearCachesIfNecessary();
4808 // Cache any prefs for the frame.
4809 mSharedContext
->CachePrefs();
4810 mProfile
.BeginFrame();
4813 // For use within CanvasRenderingContext2D, called on ReturnDrawTarget.
4814 void DrawTargetWebgl::EndFrame() {
4815 if (StaticPrefs::gfx_canvas_accelerated_debug()) {
4816 // Draw a green rectangle in the upper right corner to indicate
4818 IntRect corner
= IntRect(mSize
.width
- 16, 0, 16, 16).Intersect(GetRect());
4819 DrawRect(Rect(corner
), ColorPattern(DeviceColor(0.0f
, 1.0f
, 0.0f
, 1.0f
)),
4820 DrawOptions(), Nothing(), nullptr, false, false);
4822 mProfile
.EndFrame();
4823 // Ensure we're not somehow using more than the allowed texture memory.
4824 mSharedContext
->PruneTextureMemory();
4825 // Signal that we're done rendering the frame in case no present occurs.
4826 mSharedContext
->mWebgl
->EndOfFrame();
4827 // Check if we need to clear out any cached because of memory pressure.
4828 mSharedContext
->ClearCachesIfNecessary();
4831 bool DrawTargetWebgl::CopyToSwapChain(
4832 layers::TextureType aTextureType
, layers::RemoteTextureId aId
,
4833 layers::RemoteTextureOwnerId aOwnerId
,
4834 layers::RemoteTextureOwnerClient
* aOwnerClient
) {
4835 if (!mWebglValid
&& !FlushFromSkia()) {
4839 // Copy and swizzle the WebGL framebuffer to the swap chain front buffer.
4840 webgl::SwapChainOptions options
;
4841 options
.bgra
= true;
4842 // Allow async present to be toggled on for accelerated Canvas2D
4843 // independent of WebGL via pref.
4844 options
.forceAsyncPresent
=
4845 StaticPrefs::gfx_canvas_accelerated_async_present();
4846 options
.remoteTextureId
= aId
;
4847 options
.remoteTextureOwnerId
= aOwnerId
;
4848 return mSharedContext
->mWebgl
->CopyToSwapChain(mFramebuffer
, aTextureType
,
4849 options
, aOwnerClient
);
4852 already_AddRefed
<DrawTarget
> DrawTargetWebgl::CreateSimilarDrawTarget(
4853 const IntSize
& aSize
, SurfaceFormat aFormat
) const {
4854 return mSkia
->CreateSimilarDrawTarget(aSize
, aFormat
);
4857 bool DrawTargetWebgl::CanCreateSimilarDrawTarget(const IntSize
& aSize
,
4858 SurfaceFormat aFormat
) const {
4859 return mSkia
->CanCreateSimilarDrawTarget(aSize
, aFormat
);
4862 RefPtr
<DrawTarget
> DrawTargetWebgl::CreateClippedDrawTarget(
4863 const Rect
& aBounds
, SurfaceFormat aFormat
) {
4864 return mSkia
->CreateClippedDrawTarget(aBounds
, aFormat
);
4867 already_AddRefed
<SourceSurface
> DrawTargetWebgl::CreateSourceSurfaceFromData(
4868 unsigned char* aData
, const IntSize
& aSize
, int32_t aStride
,
4869 SurfaceFormat aFormat
) const {
4870 return mSkia
->CreateSourceSurfaceFromData(aData
, aSize
, aStride
, aFormat
);
4873 already_AddRefed
<SourceSurface
>
4874 DrawTargetWebgl::CreateSourceSurfaceFromNativeSurface(
4875 const NativeSurface
& aSurface
) const {
4876 return mSkia
->CreateSourceSurfaceFromNativeSurface(aSurface
);
4879 already_AddRefed
<SourceSurface
> DrawTargetWebgl::OptimizeSourceSurface(
4880 SourceSurface
* aSurface
) const {
4881 if (aSurface
->GetType() == SurfaceType::WEBGL
) {
4882 return do_AddRef(aSurface
);
4884 return mSkia
->OptimizeSourceSurface(aSurface
);
4887 already_AddRefed
<SourceSurface
>
4888 DrawTargetWebgl::OptimizeSourceSurfaceForUnknownAlpha(
4889 SourceSurface
* aSurface
) const {
4890 return mSkia
->OptimizeSourceSurfaceForUnknownAlpha(aSurface
);
4893 already_AddRefed
<GradientStops
> DrawTargetWebgl::CreateGradientStops(
4894 GradientStop
* aStops
, uint32_t aNumStops
, ExtendMode aExtendMode
) const {
4895 return mSkia
->CreateGradientStops(aStops
, aNumStops
, aExtendMode
);
4898 already_AddRefed
<FilterNode
> DrawTargetWebgl::CreateFilter(FilterType aType
) {
4899 return mSkia
->CreateFilter(aType
);
4902 void DrawTargetWebgl::DrawFilter(FilterNode
* aNode
, const Rect
& aSourceRect
,
4903 const Point
& aDestPoint
,
4904 const DrawOptions
& aOptions
) {
4905 MarkSkiaChanged(aOptions
);
4906 mSkia
->DrawFilter(aNode
, aSourceRect
, aDestPoint
, aOptions
);
4909 bool DrawTargetWebgl::Draw3DTransformedSurface(SourceSurface
* aSurface
,
4910 const Matrix4x4
& aMatrix
) {
4912 return mSkia
->Draw3DTransformedSurface(aSurface
, aMatrix
);
4915 void DrawTargetWebgl::PushLayer(bool aOpaque
, Float aOpacity
,
4916 SourceSurface
* aMask
,
4917 const Matrix
& aMaskTransform
,
4918 const IntRect
& aBounds
, bool aCopyBackground
) {
4919 PushLayerWithBlend(aOpaque
, aOpacity
, aMask
, aMaskTransform
, aBounds
,
4920 aCopyBackground
, CompositionOp::OP_OVER
);
4923 void DrawTargetWebgl::PushLayerWithBlend(bool aOpaque
, Float aOpacity
,
4924 SourceSurface
* aMask
,
4925 const Matrix
& aMaskTransform
,
4926 const IntRect
& aBounds
,
4927 bool aCopyBackground
,
4928 CompositionOp aCompositionOp
) {
4929 MarkSkiaChanged(DrawOptions(aOpacity
, aCompositionOp
));
4930 mSkia
->PushLayerWithBlend(aOpaque
, aOpacity
, aMask
, aMaskTransform
, aBounds
,
4931 aCopyBackground
, aCompositionOp
);
4933 SetPermitSubpixelAA(mSkia
->GetPermitSubpixelAA());
4936 void DrawTargetWebgl::PopLayer() {
4937 MOZ_ASSERT(mSkiaValid
);
4938 MOZ_ASSERT(mLayerDepth
> 0);
4941 SetPermitSubpixelAA(mSkia
->GetPermitSubpixelAA());
4944 } // namespace mozilla::gfx