Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / canvas / DrawTargetWebgl.cpp
blob9373821e87d295f3c2fcc14cca63fcdb86b67fc2
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "DrawTargetWebglInternal.h"
8 #include "SourceSurfaceWebgl.h"
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/StaticPrefs_gfx.h"
12 #include "mozilla/gfx/AAStroke.h"
13 #include "mozilla/gfx/Blur.h"
14 #include "mozilla/gfx/DrawTargetSkia.h"
15 #include "mozilla/gfx/gfxVars.h"
16 #include "mozilla/gfx/Helpers.h"
17 #include "mozilla/gfx/HelpersSkia.h"
18 #include "mozilla/gfx/Logging.h"
19 #include "mozilla/gfx/PathHelpers.h"
20 #include "mozilla/gfx/PathSkia.h"
21 #include "mozilla/gfx/Swizzle.h"
22 #include "mozilla/layers/ImageDataSerializer.h"
23 #include "mozilla/layers/RemoteTextureMap.h"
24 #include "mozilla/widget/ScreenManager.h"
25 #include "skia/include/core/SkPixmap.h"
26 #include "nsContentUtils.h"
28 #include "GLContext.h"
29 #include "WebGLContext.h"
30 #include "WebGLChild.h"
31 #include "WebGLBuffer.h"
32 #include "WebGLFramebuffer.h"
33 #include "WebGLProgram.h"
34 #include "WebGLShader.h"
35 #include "WebGLTexture.h"
36 #include "WebGLVertexArray.h"
38 #include "gfxPlatform.h"
40 #ifdef XP_MACOSX
41 # include "mozilla/gfx/ScaledFontMac.h"
42 #endif
44 namespace mozilla::gfx {
46 BackingTexture::BackingTexture(const IntSize& aSize, SurfaceFormat aFormat,
47 const RefPtr<WebGLTexture>& aTexture)
48 : mSize(aSize), mFormat(aFormat), mTexture(aTexture) {}
50 #ifdef XP_WIN
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};
55 #endif
57 SharedTexture::SharedTexture(const IntSize& aSize, SurfaceFormat aFormat,
58 const RefPtr<WebGLTexture>& aTexture)
59 : BackingTexture(aSize, aFormat, aTexture),
60 mAtlasAllocator(
61 #ifdef XP_WIN
62 aFormat == SurfaceFormat::A8
63 ? Etagere::etagere_atlas_allocator_with_options(
64 aSize.width, aSize.height, &kR8AllocatorOptions)
66 #endif
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) {
89 return nullptr;
91 RefPtr<SharedTextureHandle> handle = new SharedTextureHandle(
92 alloc.id,
93 IntRect(IntPoint(alloc.rectangle.min_x, alloc.rectangle.min_y), aSize),
94 this);
95 return handle.forget();
98 bool SharedTexture::Free(SharedTextureHandle& aHandle) {
99 if (aHandle.mTexture != this) {
100 return false;
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;
109 return true;
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;
121 if (aFullClear) {
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) {
131 if (!mSnapshot) {
132 return;
134 mSharedContext->ClearLastTexture();
135 RefPtr<SourceSurfaceWebgl> snapshot = mSnapshot.forget();
136 if (snapshot->hasOneRef()) {
137 return;
139 if (aCopyOnWrite) {
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);
143 } else {
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.
154 if (mSkia) {
155 mSkia->DetachAllSnapshots();
157 mSharedContext->ClearLastTexture(true);
158 mClipMask = nullptr;
159 mFramebuffer = nullptr;
160 mTex = nullptr;
161 mSharedContext->mDrawTargetCount--;
165 SharedContextWebgl::SharedContextWebgl() = default;
167 SharedContextWebgl::~SharedContextWebgl() {
168 // Detect context loss before deletion.
169 if (mWebgl) {
170 ExitTlsScope();
171 mWebgl->ActiveTexture(0);
173 if (mWGRPathBuilder) {
174 WGR::wgr_builder_release(mWGRPathBuilder);
175 mWGRPathBuilder = nullptr;
177 ClearAllTextures();
178 UnlinkSurfaceTextures();
179 UnlinkGlyphCaches();
182 gl::GLContext* SharedContextWebgl::GetGLContext() {
183 return mWebgl ? mWebgl->GL() : nullptr;
186 void SharedContextWebgl::EnterTlsScope() {
187 if (mTlsScope.isSome()) {
188 return;
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()) {
199 return;
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();
231 while (cache) {
232 ScaledFont* font = cache->GetFont();
233 // Access the next cache before removing the user data, as it might destroy
234 // the cache.
235 cache = cache->getNext();
236 font->RemoveUserData(&mGlyphCacheKey);
240 void SharedContextWebgl::OnMemoryPressure() { mShouldClearCaches = true; }
242 void SharedContextWebgl::ClearCaches() {
243 OnMemoryPressure();
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
256 // delete them.
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);
265 } else {
266 ++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)) {
276 return;
278 mZeroBuffer = nullptr;
279 ClearAllTextures();
280 if (mEmptyTextureMemory) {
281 ClearEmptyTextureMemory();
283 ClearLastTexture();
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);
293 mSize = size;
294 mFormat = format;
296 if (!aSharedContext || aSharedContext->IsContextLost() ||
297 aSharedContext->mDrawTargetCount >=
298 StaticPrefs::gfx_canvas_accelerated_max_draw_target_count()) {
299 return false;
301 mSharedContext = aSharedContext;
302 mSharedContext->mDrawTargetCount++;
304 if (size_t(std::max(size.width, size.height)) >
305 mSharedContext->mMaxTextureSize) {
306 return false;
309 if (!CreateFramebuffer()) {
310 return false;
313 size_t byteSize = layers::ImageDataSerializer::ComputeRGBBufferSize(
314 mSize, SurfaceFormat::B8G8R8A8);
315 if (byteSize == 0) {
316 return false;
319 size_t shmemSize = mozilla::ipc::SharedMemory::PageAlignedSize(byteSize);
320 if (NS_WARN_IF(shmemSize > UINT32_MAX)) {
321 MOZ_ASSERT_UNREACHABLE("Buffer too big?");
322 return false;
325 auto shmem = MakeRefPtr<mozilla::ipc::SharedMemory>();
326 if (NS_WARN_IF(!shmem->Create(shmemSize)) ||
327 NS_WARN_IF(!shmem->Map(shmemSize))) {
328 return false;
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)) {
339 return false;
342 // Allocate an unclipped copy of the DT pointing to its data.
343 uint8_t* dtData = nullptr;
344 IntSize dtSize;
345 int32_t dtStride = 0;
346 SurfaceFormat dtFormat = SurfaceFormat::UNKNOWN;
347 if (!mSkia->LockBits(&dtData, &dtSize, &dtStride, &dtFormat)) {
348 return false;
350 mSkiaNoClip = new DrawTargetSkia;
351 if (!mSkiaNoClip->Init(dtData, dtSize, dtStride, dtFormat, true)) {
352 mSkia->ReleaseBits(dtData);
353 return false;
355 mSkia->ReleaseBits(dtData);
357 SetPermitSubpixelAA(IsOpaque(format));
358 return true;
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) {
367 return nullptr;
369 RefPtr<SharedContextWebgl> sharedContext = new SharedContextWebgl;
370 if (!sharedContext->Initialize()) {
371 return nullptr;
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{
388 .isWebgl2 = true,
389 .resistFingerprinting = resistFingerprinting,
390 .principalKey = 0,
391 .size = {1, 1},
392 .options = options,
395 webgl::InitContextResult initResult;
396 mWebgl = WebGLContext::Create(nullptr, initDesc, &initResult);
397 if (!mWebgl) {
398 // There was a non-recoverable error when trying to create a host context.
399 sContextInitError = true;
400 mWebgl = nullptr;
401 return false;
403 if (mWebgl->IsContextLost()) {
404 mWebgl = nullptr;
405 return false;
408 mMaxTextureSize = initResult.limits.maxTex2dSize;
410 if (kIsMacOS) {
411 mRasterizationTruncates = initResult.vendor == gl::GLVendor::ATI;
414 CachePrefs();
416 if (!CreateShaders()) {
417 // There was a non-recoverable error when trying to init shaders.
418 sContextInitError = true;
419 mWebgl = nullptr;
420 return false;
423 mWGRPathBuilder = WGR::wgr_new_builder();
425 return true;
428 inline void SharedContextWebgl::BlendFunc(GLenum aSrcFactor,
429 GLenum aDstFactor) {
430 mWebgl->BlendFuncSeparate({}, aSrcFactor, aDstFactor, aSrcFactor, aDstFactor);
433 void SharedContextWebgl::SetBlendState(CompositionOp aOp,
434 const Maybe<DeviceColor>& aColor) {
435 if (aOp == mLastCompositionOp && mLastBlendColor == aColor) {
436 return;
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.
447 bool enabled = true;
448 switch (aOp) {
449 case CompositionOp::OP_OVER:
450 if (aColor) {
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);
454 } else {
455 BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
457 break;
458 case CompositionOp::OP_ADD:
459 BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE);
460 break;
461 case CompositionOp::OP_ATOP:
462 BlendFunc(LOCAL_GL_DST_ALPHA, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
463 break;
464 case CompositionOp::OP_SOURCE:
465 if (aColor) {
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
470 // a blend color.
471 mWebgl->BlendColor(aColor->b, aColor->g, aColor->r, aColor->a);
472 BlendFunc(LOCAL_GL_CONSTANT_COLOR, LOCAL_GL_ONE_MINUS_SRC_COLOR);
473 } else {
474 enabled = false;
476 break;
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);
484 break;
485 default:
486 enabled = false;
487 break;
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()) {
496 return false;
498 if (aDT != mCurrentTarget) {
499 mCurrentTarget = aDT;
500 if (aDT) {
501 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, aDT->mFramebuffer);
502 mViewportSize = aDT->GetSize();
503 mWebgl->Viewport(0, 0, mViewportSize.width, mViewportSize.height);
506 return true;
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) {
521 if (!mWebgl) {
522 return false;
524 mWebgl->ActiveTexture(1);
525 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, aTex);
526 mWebgl->ActiveTexture(0);
527 mLastClipMask = aTex;
529 return true;
532 bool SharedContextWebgl::SetNoClipMask() {
533 if (mNoClipMask) {
534 return SetClipMask(mNoClipMask);
536 if (!mWebgl) {
537 return false;
539 mNoClipMask = mWebgl->CreateTexture();
540 if (!mNoClipMask) {
541 return false;
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,
550 {1, 1, 1},
551 gfxAlphaType::NonPremult,
552 Some(Span{solidMask})});
553 InitTexParameters(mNoClipMask, false);
554 mWebgl->ActiveTexture(0);
555 mLastClipMask = mNoClipMask;
556 return true;
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)) {
564 return false;
566 // Verify the paths match.
567 if (!mPath) {
568 return !aOther.mPath;
570 if (!aOther.mPath ||
571 mPath->GetBackendType() != aOther.mPath->GetBackendType()) {
572 return false;
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);
591 return true;
593 if (!mWebglValid) {
594 // If the Skia target is currently being used, then we can't render the mask
595 // in it.
596 return false;
598 RefPtr<WebGLContext> webgl = mSharedContext->mWebgl;
599 if (!webgl) {
600 return false;
602 bool init = false;
603 if (!mClipMask) {
604 mClipMask = webgl->CreateTexture();
605 if (!mClipMask) {
606 return false;
608 init = true;
610 // Try to get the bounds of the clip to limit the size of the mask.
611 if (Maybe<IntRect> clip = mSkia->GetDeviceClipRect(true)) {
612 mClipBounds = *clip;
613 } else {
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)) {
623 return false;
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);
631 dt->SetTransform(
632 Matrix(clipStack.mTransform).PostTranslate(-mClipBounds.TopLeft()));
633 if (clipStack.mPath) {
634 dt->PushClip(clipStack.mPath);
635 } else {
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);
646 if (init) {
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
654 // necessary.
655 if (init && mClipBounds.Size() != mSize) {
656 mSharedContext->UploadSurface(nullptr, SurfaceFormat::A8, GetRect(),
657 IntPoint(), true, true);
658 init = false;
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
668 // for that here.
669 mProfile.OnCacheMiss();
670 return !!data;
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
676 // target.
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
681 // viewport.
682 if (!clip->IsEmpty() && clip->Contains(GetRect())) {
683 clip = Some(GetRect());
685 mSharedContext->SetClipRect(*clip);
686 mSharedContext->SetNoClipMask();
687 return true;
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
695 // complex.
696 if (clipStack.mPath ||
697 !clipStack.mTransform.PreservesAxisAlignedRectangles()) {
698 return false;
700 // Transform the rect and intersect it with the current clip.
701 rect =
702 clipStack.mTransform.TransformBounds(clipStack.mRect).Intersect(rect);
704 mSharedContext->SetClipRect(rect);
705 mSharedContext->SetNoClipMask();
706 return true;
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) {
712 if (!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()) {
722 return false;
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()) {
741 return false;
744 if (!Factory::AllowedSurfaceSize(aSize)) {
745 return false;
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
750 // small.
751 static const int32_t kMinDimension = 16;
752 if (std::min(aSize.width, aSize.height) < kMinDimension) {
753 return false;
756 int32_t minSize = StaticPrefs::gfx_canvas_accelerated_min_size();
757 if (aSize.width * aSize.height < minSize * minSize) {
758 return false;
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();
766 if (maxSize > 0) {
767 if (std::max(aSize.width, aSize.height) > maxSize) {
768 return false;
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)) {
781 return false;
786 return true;
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)) {
794 return nullptr;
797 RefPtr<DrawTargetWebgl> dt = new DrawTargetWebgl;
798 if (!dt->Init(aSize, aFormat, aSharedContext) || !dt->IsValid()) {
799 return nullptr;
802 return dt.forget();
805 void* DrawTargetWebgl::GetNativeSurface(NativeSurfaceType aType) {
806 switch (aType) {
807 case NativeSurfaceType::WEBGL_CONTEXT:
808 // If the context is lost, then don't attempt to access it.
809 if (mSharedContext->IsContextLost()) {
810 return nullptr;
812 if (!mWebglValid) {
813 FlushFromSkia();
815 return mSharedContext->mWebgl.get();
816 default:
817 return nullptr;
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()) {
863 return nullptr;
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();
869 if (!tex) {
870 return nullptr;
873 // If copying from a non-DT source, we have to bind a scratch framebuffer for
874 // reading.
875 if (aHandle) {
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)});
895 ClearLastTexture();
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);
907 return result;
910 inline DrawTargetWebgl::AutoRestoreContext::AutoRestoreContext(
911 DrawTargetWebgl* aTarget)
912 : mTarget(aTarget),
913 mClipAARect(aTarget->mSharedContext->mClipAARect),
914 mLastClipMask(aTarget->mSharedContext->mLastClipMask) {}
916 inline DrawTargetWebgl::AutoRestoreContext::~AutoRestoreContext() {
917 mTarget->mSharedContext->SetClipRect(mClipAARect);
918 if (mLastClipMask) {
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)) {
929 return nullptr;
931 return mSharedContext->CopySnapshot(aRect);
934 bool DrawTargetWebgl::HasDataSnapshot() const {
935 return (mSkiaValid && !mSkiaLayer) || (mSnapshot && mSnapshot->HasReadData());
938 bool DrawTargetWebgl::PrepareSkia() {
939 if (!mSkiaValid) {
940 ReadIntoSkia();
941 } else if (mSkiaLayer) {
942 FlattenSkia();
944 return mSkiaValid;
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() {
956 PrepareSkia();
957 return mSkia->Snapshot(mFormat);
960 already_AddRefed<SourceSurface> DrawTargetWebgl::Snapshot() {
961 // If already using the Skia fallback, then just snapshot that.
962 if (mSkiaValid) {
963 return GetDataSnapshot();
966 // There's no valid Skia snapshot, so we need to get one from the WebGL
967 // context.
968 if (!mSnapshot) {
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 ==
982 mSharedContext) {
983 return Snapshot();
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
997 // for reading.
998 if (aHandle) {
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);
1012 return true;
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);
1027 return true;
1030 already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshot(
1031 TextureHandle* aHandle) {
1032 // Allocate a data surface, map it, and read from the WebGL context into the
1033 // surface.
1034 SurfaceFormat format = SurfaceFormat::UNKNOWN;
1035 IntRect bounds;
1036 if (aHandle) {
1037 format = aHandle->GetFormat();
1038 bounds = aHandle->GetBounds();
1039 } else {
1040 format = mCurrentTarget->GetFormat();
1041 bounds = mCurrentTarget->GetRect();
1043 RefPtr<DataSourceSurface> surface =
1044 Factory::CreateDataSourceSurface(bounds.Size(), format);
1045 if (!surface) {
1046 return nullptr;
1048 DataSourceSurface::ScopedMap dstMap(surface, DataSourceSurface::WRITE);
1049 if (!dstMap.IsMapped() || !ReadInto(dstMap.GetData(), dstMap.GetStride(),
1050 format, bounds, aHandle)) {
1051 return nullptr;
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)) {
1059 return 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)) {
1069 return nullptr;
1071 mProfile.OnReadback();
1072 return mSharedContext->ReadSnapshot();
1075 already_AddRefed<SourceSurface> DrawTargetWebgl::GetBackingSurface() {
1076 return Snapshot();
1079 void DrawTargetWebgl::DetachAllSnapshots() {
1080 mSkia->DetachAllSnapshots();
1081 ClearSnapshot();
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
1087 // framebuffer.
1088 bool DrawTargetWebgl::MarkChanged() {
1089 if (mSnapshot) {
1090 // Try to copy the target into a new texture if possible.
1091 ClearSnapshot(true, true);
1093 if (!mWebglValid && !FlushFromSkia()) {
1094 return false;
1096 mSkiaValid = false;
1097 mIsClear = false;
1098 return true;
1101 void DrawTargetWebgl::MarkSkiaChanged(bool aOverwrite) {
1102 if (aOverwrite) {
1103 mSkiaValid = true;
1104 mSkiaLayer = false;
1105 } else if (!mSkiaValid) {
1106 if (ReadIntoSkia()) {
1107 // Signal that we've hit a complete software fallback.
1108 mProfile.OnFallback();
1110 } else if (mSkiaLayer) {
1111 FlattenSkia();
1113 mWebglValid = false;
1114 mIsClear = 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
1119 // context.
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.
1124 return true;
1125 default:
1126 return false;
1130 void DrawTargetWebgl::MarkSkiaChanged(const DrawOptions& aOptions) {
1131 if (SupportsLayering(aOptions)) {
1132 if (!mSkiaValid) {
1133 // If the Skia context needs initialization, clear it and enable layering.
1134 mSkiaValid = true;
1135 if (mWebglValid) {
1136 mProfile.OnLayer();
1137 mSkiaLayer = true;
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));
1145 } else {
1146 mSkiaNoClip->ClearRect(Rect(mSkiaNoClip->GetRect()));
1150 // The WebGL context is no longer up-to-date.
1151 mWebglValid = false;
1152 mIsClear = false;
1153 } else {
1154 // For other composition ops, just overwrite the Skia data.
1155 MarkSkiaChanged();
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) {
1164 MarkSkiaChanged();
1165 return mSkia->LockBits(aData, aSize, aStride, aFormat, aOrigin);
1167 return false;
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
1183 // buffers.
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);
1193 if (aChanged) {
1194 mWGROutputBuffer.reset(
1195 mPathVertexCapacity > 0
1196 ? new (fallible) WGR::OutputVertex[mPathVertexCapacity /
1197 sizeof(WGR::OutputVertex)]
1198 : nullptr);
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.
1228 auto vsSource =
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"
1238 "void main() {\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"
1256 "}\n";
1257 auto fsSource =
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"
1265 "void main() {\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"
1271 "}\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) {
1276 return false;
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) {
1282 return false;
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) {
1290 return false;
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) {
1301 return false;
1303 mWebgl->UseProgram(mSolidProgram);
1304 UniformData(LOCAL_GL_INT, mSolidProgramClipMask, Array<int32_t, 1>{1});
1307 if (!mImageProgram) {
1308 auto vsSource =
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"
1320 "void main() {\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"
1341 "}\n";
1342 auto fsSource =
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"
1354 "void main() {\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"
1364 "}\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) {
1369 return false;
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) {
1375 return false;
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) {
1383 return false;
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) {
1399 return false;
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});
1405 return true;
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()) {
1441 return Nothing();
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) {
1457 if (mIsClear) {
1458 // No need to clear anything if the entire framebuffer is already clear.
1459 return;
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,
1470 false);
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));
1475 } else {
1476 // Otherwise, using the transform in the shader may lead to inaccuracies, so
1477 // just fall back.
1478 MarkSkiaChanged();
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()))) {
1487 mIsClear = true;
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();
1504 if (!mTex) {
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();
1523 return true;
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.
1530 IntRect destRect =
1531 IntRect(aDestination, aSourceRect.Size()).SafeIntersect(GetRect());
1532 IntRect srcRect = destRect - aDestination + aSourceRect.TopLeft();
1533 if (srcRect.IsEmpty()) {
1534 return;
1537 if (mSkiaValid) {
1538 if (mSkiaLayer) {
1539 if (destRect.Contains(GetRect())) {
1540 // If the the destination would override the entire layer, discard the
1541 // layer.
1542 mSkiaLayer = false;
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.
1546 FlattenSkia();
1548 } else {
1549 // If there is no layer, copying is safe.
1550 MarkSkiaChanged();
1552 mSkia->CopySurface(aSurface, srcRect, destRect.TopLeft());
1553 return;
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());
1564 return;
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);
1588 return;
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,
1608 uint32_t aCount) {
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;
1621 mSkia->PopClip();
1623 mClipStack.pop_back();
1626 bool DrawTargetWebgl::RemoveAllClips() {
1627 if (mClipStack.empty()) {
1628 return true;
1630 if (!mSkia->RemoveAllClips()) {
1631 return false;
1633 mClipChanged = true;
1634 mRefreshClipState = true;
1635 mClipStack.clear();
1636 return 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);
1645 } else {
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));
1657 return true;
1660 return false;
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:
1671 return true;
1672 default:
1673 return false;
1677 static inline bool SupportsExtendMode(const SurfacePattern& aPattern) {
1678 switch (aPattern.mExtendMode) {
1679 case ExtendMode::CLAMP:
1680 return true;
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()) {
1687 return false;
1689 return true;
1690 default:
1691 return false;
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:
1699 return true;
1700 case PatternType::SURFACE: {
1701 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1702 if (!SupportsExtendMode(surfacePattern)) {
1703 return false;
1705 if (surfacePattern.mSurface) {
1706 // If the surface is already uploaded to a texture, then just use it.
1707 if (IsCompatibleSurface(surfacePattern.mSurface)) {
1708 return true;
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
1714 // limit.
1715 int32_t maxSize = int32_t(
1716 std::min(StaticPrefs::gfx_canvas_accelerated_max_surface_size(),
1717 mMaxTextureSize));
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)) {
1724 return false;
1727 return true;
1729 default:
1730 // Patterns other than colors and surfaces are currently not accelerated.
1731 return false;
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()) {
1744 return true;
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.
1774 if (!aAccelOnly) {
1775 DrawRectFallback(aRect, aPattern, aOptions, aMaskColor, aTransformed,
1776 aClipped, aStrokeOptions);
1778 return false;
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);
1790 if (aTransformed) {
1791 // If transforms are requested, then just translate back to FillRect.
1792 if (aMaskColor) {
1793 mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions);
1794 } else if (aStrokeOptions) {
1795 mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions);
1796 } else {
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());
1803 if (aMaskColor) {
1804 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1805 if (surfacePattern.mSamplingRect.IsEmpty()) {
1806 mSkia->MaskSurface(ColorPattern(*aMaskColor), surfacePattern.mSurface,
1807 aRect.TopLeft(), aOptions);
1808 } else {
1809 mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions);
1811 } else if (aStrokeOptions) {
1812 mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions);
1813 } else {
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
1819 // copy.
1820 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1821 mSkia->CopySurface(surfacePattern.mSurface,
1822 surfacePattern.mSurface->GetRect(),
1823 IntPoint::Round(aRect.TopLeft()));
1824 } else {
1825 MOZ_ASSERT(false);
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) {
1837 return do_AddRef(
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);
1851 return nullptr;
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,
1863 bool aZero,
1864 const RefPtr<WebGLTexture>& aTex) {
1865 webgl::TexUnpackBlobDesc texDesc = {
1866 LOCAL_GL_TEXTURE_2D,
1867 {uint32_t(aSrcRect.width), uint32_t(aSrcRect.height), 1}};
1868 if (aData) {
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()) {
1874 return false;
1876 int32_t stride = map.GetStride();
1877 int32_t bpp = BytesPerPixel(aFormat);
1878 // Get the data pointer range considering the sampling rect offset and
1879 // size.
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;
1890 } else if (aZero) {
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));
1894 size_t size =
1895 size_t(GetAlignedStride<4>(aSrcRect.width, BytesPerPixel(aFormat))) *
1896 aSrcRect.height;
1897 if (!mZeroBuffer || size > mZeroSize) {
1898 mZeroBuffer = mWebgl->CreateBuffer();
1899 mZeroSize = size;
1900 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mZeroBuffer);
1901 // WebGL will zero initialize the empty buffer, so we don't send zero data
1902 // explicitly.
1903 mWebgl->BufferData(LOCAL_GL_PIXEL_UNPACK_BUFFER, size, nullptr,
1904 LOCAL_GL_STATIC_DRAW);
1905 } else {
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
1913 // in the shader.
1914 GLenum intFormat =
1915 aFormat == SurfaceFormat::A8 ? LOCAL_GL_R8 : LOCAL_GL_RGBA8;
1916 GLenum extFormat =
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.
1920 if (aTex) {
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,
1925 texDesc);
1926 if (aTex) {
1927 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mLastTexture);
1929 if (!aData && aZero) {
1930 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
1932 return true;
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,
1939 bool aRenderable) {
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);
1959 if (handle) {
1960 if (wasEmpty) {
1961 // If the page was previously empty, then deduct it from the
1962 // empty memory reserves.
1963 mEmptyTextureMemory -= shared->UsedBytes();
1965 break;
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.
1971 if (!handle) {
1972 if (RefPtr<WebGLTexture> tex = mWebgl->CreateTexture()) {
1973 RefPtr<SharedTexture> shared =
1974 new SharedTexture(IntSize(pageSize, pageSize), aFormat, tex);
1975 if (aRenderable) {
1976 shared->MarkRenderable();
1978 mSharedTextures.push_back(shared);
1979 mTotalTextureMemory += shared->UsedBytes();
1980 handle = shared->Allocate(aSize);
1983 } else {
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);
1989 if (aRenderable) {
1990 standalone->MarkRenderable();
1992 mStandaloneTextures.push_back(standalone);
1993 mTotalTextureMemory += standalone->UsedBytes();
1994 handle = standalone;
1998 if (!handle) {
1999 return nullptr;
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)) {
2028 if (aTransformed) {
2029 intRect += RoundedToInt(aCurrentTransform.GetTranslation());
2031 return Some(intRect);
2034 return Nothing();
2037 Maybe<uint32_t> SharedContextWebgl::GetUniformLocation(
2038 const RefPtr<WebGLProgram>& aProg, const std::string& aName) const {
2039 if (!aProg || !aProg->LinkInfo()) {
2040 return Nothing();
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);
2048 if (indexed) {
2049 locName = indexed->name;
2052 const auto baseLength = locName.size();
2053 for (const auto& pair : activeUniform.locByIndex) {
2054 if (indexed) {
2055 locName.erase(baseLength); // Erase previous "[N]".
2056 locName += '[';
2057 locName += std::to_string(pair.first);
2058 locName += ']';
2060 if (locName == aName || locName == aName + "[0]") {
2061 return Some(pair.second);
2066 return Nothing();
2069 template <class T>
2070 struct IsUniformDataValT : std::false_type {};
2071 template <>
2072 struct IsUniformDataValT<webgl::UniformDataVal> : std::true_type {};
2073 template <>
2074 struct IsUniformDataValT<float> : std::true_type {};
2075 template <>
2076 struct IsUniformDataValT<int32_t> : std::true_type {};
2077 template <>
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()) {
2134 return true;
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.
2147 if (!aAccelOnly) {
2148 MOZ_ASSERT(!aVertexRange);
2149 mCurrentTarget->DrawRectFallback(aRect, aPattern, aOptions, aMaskColor,
2150 aTransformed, aClipped, aStrokeOptions);
2152 return false;
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;
2159 if (aRectXform) {
2160 rectXform.PreMultiply(*aRectXform);
2163 if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE && aTransformed &&
2164 aClipped &&
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,
2179 aRectXform);
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,
2188 aRectXform);
2191 // Set up the scissor test to reflect the clipping rectangle, if supplied.
2192 if (!mClipRect.Contains(IntRect(IntPoint(), mViewportSize))) {
2193 EnableScissor(mClipRect);
2194 } else {
2195 DisableScissor();
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
2221 // viewport.
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) {
2228 color =
2229 PremultiplyColor(mCurrentTarget->GetClearPattern().mColor);
2231 mWebgl->ClearColor(color.b, color.g, color.r, color.a);
2232 mWebgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT);
2233 success = true;
2234 break;
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
2267 // boundary.
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);
2276 if (aTransformed) {
2277 xform *= rectXform;
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.
2288 if (aVertexRange) {
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);
2292 } else {
2293 // Otherwise we're drawing a simple filled rectangle.
2294 DrawQuad();
2296 success = true;
2297 break;
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))
2309 : nullptr);
2310 IntSize texSize;
2311 IntPoint offset;
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();
2324 } else {
2325 // Otherwise, there is no handle that can be used yet, so extract
2326 // information from the surface pattern.
2327 handle = nullptr;
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.
2331 break;
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()) {
2349 break;
2351 if (aRectXform) {
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;
2358 IntRect bounds;
2359 IntSize backingSize;
2360 RefPtr<DataSourceSurface> data;
2361 if (handle) {
2362 if (aForceUpdate) {
2363 data = surfacePattern.mSurface->GetDataSurface();
2364 if (!data) {
2365 break;
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.
2374 handle->remove();
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();
2381 } else {
2382 // If we get here, we need a data surface for a texture upload.
2383 data = surfacePattern.mSurface->GetDataSurface();
2384 if (!data) {
2385 break;
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(
2391 format, texSize,
2392 !aForceUpdate && surfacePattern.mExtendMode == ExtendMode::CLAMP);
2393 if (!handle) {
2394 MOZ_ASSERT(false);
2395 break;
2397 // Link the handle to the surface's user data.
2398 handle->SetSamplingOffset(surfacePattern.mSamplingRect.TopLeft());
2399 if (aHandle) {
2400 *aHandle = handle;
2401 } else {
2402 handle->SetSurface(surfacePattern.mSurface);
2403 surfacePattern.mSurface->AddUserData(&mTextureHandleKey, handle.get(),
2404 nullptr);
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
2429 ? 0.0f
2430 : 1.0f};
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
2435 // boundary.
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);
2442 DeviceColor color =
2443 mLastCompositionOp == CompositionOp::OP_CLEAR
2444 ? DeviceColor(1, 1, 1, 1)
2445 : PremultiplyColor(
2446 aMaskColor && format != SurfaceFormat::A8
2447 ? DeviceColor::Mask(1.0f, aMaskColor->a)
2448 : aMaskColor.valueOr(DeviceColor(1, 1, 1, 1)),
2449 aOptions.mAlpha);
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);
2453 if (aTransformed) {
2454 xform *= rectXform;
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;
2469 if (handle) {
2470 backing = handle->GetBackingTexture();
2471 if (!tex) {
2472 tex = backing->GetWebGLTexture();
2474 bounds = handle->GetBounds();
2475 backingSize = backing->GetSize();
2477 if (mLastTexture != tex) {
2478 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex);
2479 mLastTexture = 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);
2496 if (data) {
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();
2501 } else {
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;
2533 break;
2534 case ExtendMode::REPEAT_X:
2535 texBounds[0] = -1e16f;
2536 texBounds[2] = 1e16f;
2537 break;
2538 case ExtendMode::REPEAT_Y:
2539 texBounds[1] = -1e16f;
2540 texBounds[3] = 1e16f;
2541 break;
2542 default:
2543 break;
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.
2554 if (aVertexRange) {
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);
2558 } else {
2559 // Otherwise we're drawing a simple filled rectangle.
2560 DrawQuad();
2563 // Restore the default linear filter if overridden.
2564 if (UseNearestFilter(surfacePattern)) {
2565 SetTexFilter(tex, true);
2568 success = true;
2569 break;
2571 default:
2572 gfxWarning() << "Unknown DrawTargetWebgl::DrawRect pattern type: "
2573 << (int)aPattern.GetType();
2574 break;
2577 return success;
2580 bool SharedContextWebgl::RemoveSharedTexture(
2581 const RefPtr<SharedTexture>& aTexture) {
2582 auto pos =
2583 std::find(mSharedTextures.begin(), mSharedTextures.end(), aTexture);
2584 if (pos == mSharedTextures.end()) {
2585 return false;
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()
2591 << 20;
2592 size_t usedBytes = aTexture->UsedBytes();
2593 if (mEmptyTextureMemory + usedBytes <= maxBytes) {
2594 mEmptyTextureMemory += usedBytes;
2595 } else {
2596 mTotalTextureMemory -= usedBytes;
2597 mSharedTextures.erase(pos);
2598 ClearLastTexture();
2600 return true;
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(),
2616 aTexture);
2617 if (pos == mStandaloneTextures.end()) {
2618 return false;
2620 mTotalTextureMemory -= aTexture->UsedBytes();
2621 mStandaloneTextures.erase(pos);
2622 ClearLastTexture();
2623 return true;
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()) {
2639 entry->Unlink();
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
2649 // surface.
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();
2679 dir = dir / len;
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),
2702 gradient.mStops));
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,
2711 gradBegin.y)
2712 .PreTranslate(minDist - 1, 0));
2713 if (SupportsPattern(surfacePattern)) {
2714 return Some(surfacePattern);
2719 return Nothing();
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
2729 // issues.
2730 DrawRect(*clipped, aPattern, aOptions, Nothing(), nullptr, false);
2731 return;
2734 if (RectInsidePrecisionLimits(xformRect)) {
2735 if (SupportsPattern(aPattern)) {
2736 DrawRect(aRect, aPattern, aOptions);
2737 return;
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,
2743 true)) {
2744 return;
2750 if (!mWebglValid) {
2751 MarkSkiaChanged(aOptions);
2752 mSkia->FillRect(aRect, aPattern, aOptions);
2753 } else {
2754 // If the pattern is unsupported, then transform the rect to a path so it
2755 // can be cached.
2756 SkPath skiaPath;
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) {
2764 mHandle = 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.
2773 if (mHandle) {
2774 mHandle->SetCacheEntry(nullptr);
2775 mHandle = nullptr;
2778 RemoveFromList();
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
2784 // disambiguation.
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);
2807 if (aPattern) {
2808 hash = AddToHash(hash, (int)aPattern->GetType());
2810 return hash;
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
2824 // pattern.
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 &&
2838 aPath == mPath &&
2839 (!aPattern ? !mPattern : mPattern && *aPattern == *mPattern) &&
2840 (!aStrokeOptions
2841 ? !mStrokeOptions
2842 : mStrokeOptions && *aStrokeOptions == *mStrokeOptions &&
2843 mAAStrokeMode == aStrokeMode) &&
2844 aSigma == mSigma;
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,
2852 float aSigma)
2853 : CacheEntryImpl<PathCacheEntry>(aTransform, aBounds, aHash),
2854 mPath(std::move(aPath)),
2855 mOrigin(aOrigin),
2856 mPattern(aPattern),
2857 mStrokeOptions(aStrokeOptions),
2858 mAAStrokeMode(aStrokeMode),
2859 mSigma(aSigma) {}
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,
2869 float aSigma) {
2870 HashNumber hash =
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;
2879 if (aPattern) {
2880 pattern = aPattern->CloneWeak();
2881 if (!pattern) {
2882 return nullptr;
2885 StoredStrokeOptions* strokeOptions = nullptr;
2886 if (aStrokeOptions) {
2887 strokeOptions = aStrokeOptions->Clone();
2888 if (!strokeOptions) {
2889 return nullptr;
2892 RefPtr<PathCacheEntry> entry =
2893 new PathCacheEntry(std::move(aPath), pattern, strokeOptions, aStrokeMode,
2894 aTransform, aBounds, aOrigin, hash, aSigma);
2895 Insert(entry);
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) {
2902 return;
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
2915 // issues.
2916 DrawRect(*clipped, aPattern, aOptions, Nothing(), nullptr, false);
2917 return;
2921 if (RectInsidePrecisionLimits(xformRect)) {
2922 if (SupportsPattern(aPattern)) {
2923 DrawRect(NarrowToFloat(rect), aPattern, aOptions);
2924 return;
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)) {
2931 return;
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) {
2980 return Nothing();
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);
2990 SkPoint params[4];
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);
3002 break;
3004 case SkPath::kLine_Verb: {
3005 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
3006 WGR::wgr_builder_line_to(aPathBuilder, p1.x, p1.y);
3007 break;
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,
3016 p3.y);
3017 break;
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);
3024 break;
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,
3037 // q2.y);
3038 WGR::wgr_builder_quad_to(aPathBuilder, q1.x, q1.y, q2.x, q2.y);
3040 break;
3042 case SkPath::kClose_Verb:
3043 // printf_stderr("close\n");
3044 WGR::wgr_builder_close(aPathBuilder);
3045 break;
3046 default:
3047 MOZ_ASSERT(false);
3048 // Unexpected verb found in path!
3049 return Nothing();
3053 WGR::Path p = WGR::wgr_builder_get_path(aPathBuilder);
3054 if (!p.num_points || !p.num_types) {
3055 WGR::wgr_path_release(p);
3056 return Nothing();
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);
3071 return Nothing();
3073 return Some(vb);
3076 static inline AAStroke::LineJoin ToAAStrokeLineJoin(JoinStyle aJoin) {
3077 switch (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) {
3090 switch (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) +
3107 Point(0.5f, 0.5f);
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)) {
3120 return Nothing();
3122 AAStroke::Stroker* s = AAStroke::aa_stroke_new(
3123 &style, (AAStroke::OutputVertex*)aBuffer, aBufferCapacity);
3124 bool valid = true;
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) {
3130 valid = false;
3131 break;
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) {
3138 break;
3141 // Check if the path is closed. This is a flag modifying the last type.
3142 bool closed =
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) {
3151 valid = false;
3152 break;
3154 Point p1 = WGRPointToPoint(aPath.mPath.points[curPoint]);
3155 AAStroke::aa_stroke_move_to(s, p1.x, p1.y, closed);
3156 if (end) {
3157 AAStroke::aa_stroke_line_to(s, p1.x, p1.y, true);
3159 curPoint++;
3160 break;
3162 case WGR::PathPointTypeLine: {
3163 if (curPoint + 1 > aPath.mPath.num_points) {
3164 valid = false;
3165 break;
3167 Point p1 = WGRPointToPoint(aPath.mPath.points[curPoint]);
3168 AAStroke::aa_stroke_line_to(s, p1.x, p1.y, end);
3169 curPoint++;
3170 break;
3172 case WGR::PathPointTypeBezier: {
3173 if (curPoint + 3 > aPath.mPath.num_points) {
3174 valid = false;
3175 break;
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,
3181 end);
3182 curPoint += 3;
3183 break;
3185 default:
3186 MOZ_ASSERT(false, "Unknown WGR path point type");
3187 valid = false;
3188 break;
3191 // Close the sub-path if necessary.
3192 if (valid && closed) {
3193 AAStroke::aa_stroke_close(s);
3196 Maybe<AAStroke::VertexBuffer> result;
3197 if (valid) {
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);
3201 } else {
3202 result = Some(vb);
3205 AAStroke::aa_stroke_release(s);
3206 return result;
3209 // Search the path cache for any entries stored in the path vertex buffer and
3210 // remove them.
3211 void PathCache::ClearVertexRanges() {
3212 for (auto& chain : mChains) {
3213 PathCacheEntry* entry = chain.getFirst();
3214 while (entry) {
3215 PathCacheEntry* next = entry->getNext();
3216 if (entry->GetVertexRange().IsValid()) {
3217 entry->Unlink();
3219 entry = next;
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
3232 // mask.
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 *
3246 aOptions.mAlpha <
3247 1.0f &&
3248 !aAllowStrokeAlpha
3249 ? AAStrokeMode::Mask
3250 : AAStrokeMode::Geometry;
3252 return AAStrokeMode::Unsupported;
3253 default:
3254 return AAStrokeMode::Unsupported;
3258 // Render an AA-Stroke'd vertex range into an R8 mask texture for subsequent
3259 // drawing.
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);
3265 if (!handle) {
3266 return nullptr;
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());
3279 ClearLastTexture();
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.
3297 } else {
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;
3331 SetNoClipMask();
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);
3339 if (prevClipMask) {
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
3357 // path.
3358 if (aPathXform) {
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()) {
3364 return true;
3366 // Avoid integer conversion errors with abnormally large paths.
3367 if (!RectInsidePrecisionLimits(bounds)) {
3368 return false;
3370 IntRect viewport(IntPoint(), mViewportSize);
3371 if (aShadow) {
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();
3379 if (aCacheable) {
3380 // Quantize the path origin to increase the reuse of cache entries.
3381 bounds.Scale(4.0f);
3382 bounds.Round();
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()) {
3389 return true;
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
3395 // path texture.
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)
3401 : Nothing());
3402 AAStrokeMode aaStrokeMode =
3403 aStrokeOptions && mPathAAStroke
3404 ? SupportsAAStroke(aPattern, aOptions, *aStrokeOptions,
3405 aAllowStrokeAlpha)
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;
3411 if (aCacheable) {
3412 if (!mPathCache) {
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);
3419 if (!qp) {
3420 return false;
3422 entry = mPathCache->FindOrInsertEntry(
3423 std::move(*qp), color ? nullptr : &aPattern, aStrokeOptions,
3424 aaStrokeMode, currentTransform, intBounds, quantizedOrigin,
3425 aShadow ? aShadow->mSigma : -1.0f);
3426 if (!entry) {
3427 return false;
3429 handle = entry->GetHandle();
3432 // If there is a shadow, it needs to draw with the shadow color rather than
3433 // the path color.
3434 Maybe<DeviceColor> shadowColor = color;
3435 if (aShadow && aOptions.mCompositionOp != CompositionOp::OP_CLEAR) {
3436 shadowColor = Some(aShadow->mColor);
3437 if (color) {
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.
3450 Point offset =
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
3466 // location.
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);
3495 } else {
3496 strokeVB = Some(vb);
3500 if (!strokeVB) {
3501 wgrVB = GeneratePathVertexBuffer(
3502 entry->GetPath(), IntRect(-intBounds.TopLeft(), mViewportSize),
3503 mRasterizationTruncates, outputBuffer, outputBufferCapacity);
3505 } else {
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
3523 // space.
3524 Rect invRect = invTransform.TransformBounds(Rect(mClipRect));
3525 invRect.RoundOut();
3526 cullRect = Some(invRect);
3528 SkPath fillPath;
3529 if (pathSkia->GetFillPath(*aStrokeOptions, pathXform, fillPath,
3530 cullRect)) {
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
3558 // for it.
3559 if (mPathCache) {
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)),
3570 uint32_t(vbLen));
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;
3579 if (wgrVB) {
3580 WGR::wgr_vertex_buffer_release(wgrVB.ref());
3581 } else {
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.
3589 if (entry) {
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);
3600 } else {
3601 // Remember the vertex range in the cache entry so that it can be
3602 // reused later.
3603 if (entry) {
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);
3613 } else {
3614 if (wgrVB) {
3615 WGR::wgr_vertex_buffer_release(wgrVB.ref());
3616 } else {
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)) {
3633 return false;
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.
3641 handle = nullptr;
3642 RefPtr<DrawTargetSkia> pathDT = new DrawTargetSkia;
3643 if (pathDT->Init(intBounds.Size(), color || aShadow
3644 ? SurfaceFormat::A8
3645 : SurfaceFormat::B8G8R8A8)) {
3646 Point offset = -quantBounds.TopLeft();
3647 if (aShadow) {
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.
3665 path = aPath;
3666 pathDT->SetTransform(pathXform * Matrix::Translation(offset));
3667 } else {
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);
3677 } else {
3678 pathDT->Fill(path, cachePattern, drawOptions);
3681 if (aShadow && aShadow->mSigma > 0.0f) {
3682 // Blur the shadow if required.
3683 uint8_t* data = nullptr;
3684 IntSize size;
3685 int32_t stride = 0;
3686 SurfaceFormat format = SurfaceFormat::UNKNOWN;
3687 if (pathDT->LockBits(&data, &size, &stride, &format)) {
3688 AlphaBoxBlur blur(Rect(pathDT->GetRect()), stride, aShadow->mSigma,
3689 aShadow->mSigma);
3690 blur.Blur(data);
3691 pathDT->ReleaseBits(data);
3694 RefPtr<SourceSurface> pathSurface = pathDT->Snapshot();
3695 if (pathSurface) {
3696 // If the target changed, try to restore it.
3697 if (mCurrentTarget != oldTarget && !oldTarget->PrepareContext()) {
3698 return false;
3700 SurfacePattern pathPattern(pathSurface, ExtendMode::CLAMP,
3701 Matrix::Translation(quantBounds.TopLeft()),
3702 filter);
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) &&
3709 handle) {
3710 if (entry) {
3711 entry->Link(handle);
3713 } else if (entry) {
3714 entry->Unlink();
3716 return true;
3720 return false;
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
3728 // fallbacks.
3729 if (ShouldAccelPath(aOptions, aStrokeOptions) &&
3730 mSharedContext->DrawPathAccel(aPath, aPattern, aOptions, aStrokeOptions,
3731 aAllowStrokeAlpha)) {
3732 return;
3735 // There was no path cache entry available to use, so fall back to drawing the
3736 // path with Skia.
3737 MarkSkiaChanged(aOptions);
3738 if (aStrokeOptions) {
3739 mSkia->Stroke(aPath, aPattern, *aStrokeOptions, aOptions);
3740 } else {
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,
3767 aStrokeOptions)) {
3768 return;
3771 MarkSkiaChanged(aOptions);
3772 if (aStrokeOptions) {
3773 mSkia->StrokeCircle(aOrigin, aRadius, aPattern, *aStrokeOptions, aOptions);
3774 } else {
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);
3799 return;
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);
3813 } else {
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,
3818 Some(sourceColor));
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();
3826 if (!surfaceData) {
3827 return nullptr;
3829 DataSourceSurface::ScopedMap srcMap(surfaceData, DataSourceSurface::READ);
3830 if (!srcMap.IsMapped()) {
3831 return nullptr;
3833 IntSize size = surfaceData->GetSize();
3834 RefPtr<DataSourceSurface> alpha =
3835 Factory::CreateDataSourceSurface(size, SurfaceFormat::A8, false);
3836 if (!alpha) {
3837 return nullptr;
3839 DataSourceSurface::ScopedMap dstMap(alpha, DataSourceSurface::WRITE);
3840 if (!dstMap.IsMapped()) {
3841 return nullptr;
3843 // For subpixel masks, ignore the alpha and instead sample one of the color
3844 // channels as if they were alpha.
3845 SwizzleData(
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) {
3857 return;
3860 // If there is a WebGL context, then try to cache the path to avoid slow
3861 // fallbacks.
3862 if (ShouldAccelPath(aOptions, aStrokeOptions) &&
3863 mSharedContext->DrawPathAccel(aPath, aPattern, aOptions, aStrokeOptions,
3864 false, &aShadow)) {
3865 return;
3868 // There was no path cache entry available to use, so fall back to drawing the
3869 // path with Skia.
3870 MarkSkiaChanged(aOptions);
3871 mSkia->DrawShadow(aPath, aPattern, aShadow, aOptions, aStrokeOptions);
3874 void DrawTargetWebgl::DrawSurfaceWithShadow(SourceSurface* aSurface,
3875 const Point& aDest,
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));
3882 SkPath skiaPath;
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,
3888 &aShadow, false)) {
3889 DrawRect(Rect(aSurface->GetRect()) + aDest, pattern, options);
3890 return;
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) {
3911 if (!mWebglValid) {
3912 MarkSkiaChanged(aOptions);
3913 mSkia->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions);
3914 } else {
3915 // If the stroke options are unsupported, then transform the rect to a path
3916 // so it can be cached.
3917 SkPath skiaPath;
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,
3934 bool aClosed) {
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
3941 // actual cap used.
3942 CapStyle capStyle =
3943 aClosed ? (aStrokeOptions.mLineJoin == JoinStyle::ROUND ? CapStyle::ROUND
3944 : CapStyle::BUTT)
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;
3957 Point dirY;
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.
3962 switch (capStyle) {
3963 case CapStyle::BUTT:
3964 // The cap doesn't extend beyond the line so nothing is drawn.
3965 return true;
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;
3973 break;
3975 } else {
3976 // Make the scale map to a single unit length.
3977 scale /= dirLen;
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)) {
3992 return true;
3995 return false;
3998 void DrawTargetWebgl::StrokeLine(const Point& aStart, const Point& aEnd,
3999 const Pattern& aPattern,
4000 const StrokeOptions& aStrokeOptions,
4001 const DrawOptions& aOptions) {
4002 if (!mWebglValid) {
4003 MarkSkiaChanged(aOptions);
4004 mSkia->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions);
4005 } else if (!StrokeLineAccel(aStart, aEnd, aPattern, aStrokeOptions,
4006 aOptions)) {
4007 // If the stroke options are unsupported, then transform the line to a path
4008 // so it can be cached.
4009 SkPath skiaPath;
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) {
4021 return;
4023 const auto& skiaPath = static_cast<const PathSkia*>(aPath)->GetPath();
4024 if (!mWebglValid) {
4025 MarkSkiaChanged(aOptions);
4026 mSkia->Stroke(aPath, aPattern, aStrokeOptions, aOptions);
4027 return;
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) {
4036 uint8_t verbs[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,
4044 closed)) {
4045 if (closed) {
4046 StrokeLineAccel(end, start, aPattern, aStrokeOptions, aOptions, true);
4048 return;
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) {
4083 return;
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)) {
4092 return;
4095 if (useSubpixelAA) {
4096 // Subpixel AA does not support layering because the subpixel masks can't
4097 // blend with the over op.
4098 MarkSkiaChanged();
4099 } else {
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
4118 // hinting.
4119 static inline IntPoint QuantizeScale(ScaledFont* aFont,
4120 const Matrix& aTransform) {
4121 if (!aFont->UseSubpixelPosition()) {
4122 return {1, 1};
4124 if (aTransform._12 == 0) {
4125 // Glyphs are rendered subpixel horizontally, so snap vertically.
4126 return {4, 1};
4128 if (aTransform._11 == 0) {
4129 // Glyphs are rendered subpixel vertically, so snap horizontally.
4130 return {1, 4};
4132 // The transform isn't aligned, so don't snap.
4133 return {4, 4};
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) {
4156 IntPoint offset =
4157 RoundedToInt(aTransform.TransformPoint(aBuffer.mGlyphs[0].mPosition));
4158 offset.x.value &= ~(aQuantizeScale.x.value - 1);
4159 offset.y.value &= ~(aQuantizeScale.y.value - 1);
4160 return offset;
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
4165 // hashing.
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);
4178 return hash;
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)) {
4192 return false;
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,
4200 src.mPosition))) {
4201 return false;
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)) {
4208 return false;
4210 } else if (mStrokeOptions) {
4211 // If not stroking, check if the entry is stroked. If so, don't match.
4212 return false;
4214 // Verify that the full bounds, once translated and clipped, are equal to the
4215 // clipped bounds.
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),
4229 mColor(aColor),
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);
4270 return nullptr;
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) {
4283 return nullptr;
4286 RefPtr<GlyphCacheEntry> entry =
4287 new GlyphCacheEntry(aBuffer, aColor, aTransform, aQuantizeScale, aBounds,
4288 aFullBounds, aHash, strokeOptions);
4289 Insert(entry);
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) {
4302 return false;
4304 uint32_t whitespace = *mLastWhitespace;
4305 for (size_t i = 0; i < aBuffer.mNumGlyphs; ++i) {
4306 if (aBuffer.mGlyphs[i].mIndex != whitespace) {
4307 return false;
4310 return true;
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) {
4326 return false;
4328 RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface();
4329 if (!dataSurf) {
4330 return true;
4332 DataSourceSurface::ScopedMap map(dataSurf, DataSourceSurface::READ);
4333 if (!map.IsMapped()) {
4334 return true;
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;
4346 gray |= gray << 8;
4347 gray |= gray << 16;
4348 if (color != gray) return true;
4350 data += stride;
4352 return false;
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
4364 // ramp tables.
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
4369 // G channel.
4370 g = (r * 54 + g * 183 + b * 19) >> 8;
4371 g &= floorMask;
4372 r = g;
4373 b = g;
4374 } else {
4375 r &= floorMask;
4376 g &= floorMask;
4377 b &= floorMask;
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.
4392 GlyphCache* cache =
4393 static_cast<GlyphCache*>(aFont->GetUserData(&mGlyphCacheKey));
4394 if (!cache) {
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)) {
4403 return true;
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
4423 // applied.
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
4429 // AA.
4430 bool usePreblend =
4431 aUseSubpixelAA || aOptions.mAntialiasMode != AntialiasMode::NONE;
4432 #else
4433 // FreeType backends currently don't use any preblending.
4434 bool usePreblend = false;
4435 #endif
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);
4443 HashNumber hash =
4444 GlyphCacheEntry::HashGlyphs(aBuffer, quantizeTransform, quantizeScale);
4445 DeviceColor colorOrMask =
4446 useBitmaps ? color
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);
4453 if (!entry) {
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);
4459 if (!bounds) {
4460 // Assume the buffer is full of whitespace characters that should be
4461 // remembered for subsequent lookups.
4462 cache->SetLastWhitespace(aBuffer);
4463 return true;
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()) {
4470 return true;
4472 IntRect fullBounds = RoundedOut(xformBounds);
4473 IntRect clipBounds = fullBounds.Intersect(clipRect);
4474 // Check if the bounds are completely clipped out.
4475 if (clipBounds.IsEmpty()) {
4476 return true;
4478 entry = cache->InsertEntry(aBuffer, colorOrMask, quantizeTransform,
4479 quantizeScale, clipBounds, fullBounds, hash,
4480 aStrokeOptions);
4481 if (!entry) {
4482 return false;
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
4489 // offset.
4490 IntRect intBounds = entry->GetBounds();
4491 IntPoint newOffset =
4492 QuantizeOffset(quantizeTransform, quantizeScale, aBuffer);
4493 intBounds +=
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
4505 // texture.
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,
4510 true, true)) {
4511 return true;
4513 } else {
4514 handle = nullptr;
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,
4535 drawOptions);
4536 } else {
4537 ColorPattern colorPattern(useBitmaps ? color : DeviceColor(1, 1, 1, 1));
4538 if (aStrokeOptions) {
4539 textDT->StrokeGlyphs(aFont, aBuffer, colorPattern, *aStrokeOptions,
4540 drawOptions);
4541 } else {
4542 textDT->FillGlyphs(aFont, aBuffer, colorPattern, drawOptions);
4545 RefPtr<SourceSurface> textSurface = textDT->Snapshot();
4546 if (textSurface) {
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);
4554 if (!textSurface) {
4555 // Failed extracting alpha for the text surface...
4556 return false;
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,
4565 true) &&
4566 handle) {
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);
4570 } else {
4571 // If drawing failed, remove the entry from the cache.
4572 entry->Unlink();
4574 return true;
4578 return false;
4581 void DrawTargetWebgl::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
4582 const Pattern& aPattern,
4583 const DrawOptions& aOptions) {
4584 if (!aFont || !aBuffer.mNumGlyphs) {
4585 return;
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)) {
4594 return;
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.
4602 MarkSkiaChanged();
4603 } else {
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() {
4611 if (mSkiaValid) {
4612 return false;
4614 bool didReadback = false;
4615 if (mWebglValid) {
4616 uint8_t* data = nullptr;
4617 IntSize size;
4618 int32_t stride;
4619 SurfaceFormat format;
4620 if (mIsClear) {
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));
4625 } else {
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));
4636 didReadback = true;
4639 mSkiaValid = true;
4640 // The Skia data is flat after reading, so disable any layering.
4641 mSkiaLayer = false;
4642 return didReadback;
4645 // Reads data from the WebGL context and blends it with the current Skia layer.
4646 void DrawTargetWebgl::FlattenSkia() {
4647 if (!mSkiaValid || !mSkiaLayer) {
4648 return;
4650 mSkiaLayer = false;
4651 if (mSkiaLayerClear) {
4652 // If the WebGL target is clear, then there is nothing to blend.
4653 return;
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;
4668 return false;
4670 // The WebGL target is already valid, so there is nothing to do.
4671 if (mWebglValid) {
4672 return true;
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.
4677 mWebglValid = true;
4678 if (mSkiaValid) {
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.
4683 if (mIsClear) {
4684 if (!DrawRect(Rect(GetRect()), GetClearPattern(),
4685 DrawOptions(1.0f, CompositionOp::OP_SOURCE), Nothing(),
4686 nullptr, false, false, true)) {
4687 mWebglValid = false;
4688 return false;
4690 return true;
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;
4698 return false;
4701 // If there is no layer, then just upload it directly.
4702 if (!mSkiaLayer) {
4703 if (PrepareContext(false) && MarkChanged()) {
4704 if (RefPtr<DataSourceSurface> data = skiaSnapshot->GetDataSurface()) {
4705 mSharedContext->UploadSurface(data, mFormat, GetRect(), IntPoint(),
4706 false, false, mTex);
4707 return true;
4710 // Failed to upload the Skia snapshot.
4711 mWebglValid = false;
4712 return 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;
4723 return false;
4726 return true;
4729 void DrawTargetWebgl::UsageProfile::BeginFrame() {
4730 // Reset the usage profile counters for the new frame.
4731 mFallbacks = 0;
4732 mLayers = 0;
4733 mCacheMisses = 0;
4734 mCacheHits = 0;
4735 mUncachedDraws = 0;
4736 mReadbacks = 0;
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.
4744 float cacheRatio =
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)) {
4750 failed = true;
4752 if (failed) {
4753 ++mFailedFrames;
4755 ++mFrameCount;
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) {
4764 return false;
4766 float failRatio =
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;
4775 if (mPathCache) {
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
4793 // context.
4794 if (!mWebglValid) {
4795 if (aInvalidContents) {
4796 // If nothing needs to persist, just mark the WebGL context valid.
4797 mWebglValid = true;
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.
4801 mIsClear = false;
4802 } else {
4803 FlushFromSkia();
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
4817 // acceleration.
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()) {
4836 return false;
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) {
4911 MarkSkiaChanged();
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);
4932 ++mLayerDepth;
4933 SetPermitSubpixelAA(mSkia->GetPermitSubpixelAA());
4936 void DrawTargetWebgl::PopLayer() {
4937 MOZ_ASSERT(mSkiaValid);
4938 MOZ_ASSERT(mLayerDepth > 0);
4939 --mLayerDepth;
4940 mSkia->PopLayer();
4941 SetPermitSubpixelAA(mSkia->GetPermitSubpixelAA());
4944 } // namespace mozilla::gfx