Bug 1858921 - Part 6: Remove unused default template arguments r=sfink
[gecko.git] / dom / canvas / DrawTargetWebgl.cpp
blob6781f2a90445b62e472baa79a1454bbc3cfac53c
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/PathSkia.h"
20 #include "mozilla/gfx/Swizzle.h"
21 #include "mozilla/layers/ImageDataSerializer.h"
22 #include "skia/include/core/SkPixmap.h"
24 #include "ClientWebGLContext.h"
25 #include "WebGLChild.h"
27 #include "gfxPlatform.h"
29 namespace mozilla::gfx {
31 // Inserts (allocates) a rectangle of the requested size into the tree.
32 Maybe<IntPoint> TexturePacker::Insert(const IntSize& aSize) {
33 // Check if the available space could possibly fit the requested size. If
34 // not, there is no reason to continue searching within this sub-tree.
35 if (mAvailable < std::min(aSize.width, aSize.height) ||
36 mBounds.width < aSize.width || mBounds.height < aSize.height) {
37 return Nothing();
39 if (mChildren) {
40 // If this node has children, then try to insert into each of the children
41 // in turn.
42 Maybe<IntPoint> inserted = mChildren[0].Insert(aSize);
43 if (!inserted) {
44 inserted = mChildren[1].Insert(aSize);
46 // If the insertion succeeded, adjust the available state to reflect the
47 // remaining space in the children.
48 if (inserted) {
49 mAvailable = std::max(mChildren[0].mAvailable, mChildren[1].mAvailable);
50 if (!mAvailable) {
51 DiscardChildren();
54 return inserted;
56 // If we get here, we've encountered a leaf node. First check if its size is
57 // exactly the requested size. If so, mark the node as unavailable and return
58 // its offset.
59 if (mBounds.Size() == aSize) {
60 mAvailable = 0;
61 return Some(mBounds.TopLeft());
63 // The node is larger than the requested size. Choose the axis which has the
64 // most excess space beyond the requested size and split it so that at least
65 // one of the children matches the requested size for that axis.
66 if (mBounds.width - aSize.width > mBounds.height - aSize.height) {
67 mChildren.reset(new TexturePacker[2]{
68 TexturePacker(
69 IntRect(mBounds.x, mBounds.y, aSize.width, mBounds.height)),
70 TexturePacker(IntRect(mBounds.x + aSize.width, mBounds.y,
71 mBounds.width - aSize.width, mBounds.height))});
72 } else {
73 mChildren.reset(new TexturePacker[2]{
74 TexturePacker(
75 IntRect(mBounds.x, mBounds.y, mBounds.width, aSize.height)),
76 TexturePacker(IntRect(mBounds.x, mBounds.y + aSize.height,
77 mBounds.width, mBounds.height - aSize.height))});
79 // After splitting, try to insert into the first child, which should usually
80 // be big enough to accomodate the request. Adjust the available state to the
81 // remaining space.
82 Maybe<IntPoint> inserted = mChildren[0].Insert(aSize);
83 mAvailable = std::max(mChildren[0].mAvailable, mChildren[1].mAvailable);
84 return inserted;
87 // Removes (frees) a rectangle with the given bounds from the tree.
88 bool TexturePacker::Remove(const IntRect& aBounds) {
89 if (!mChildren) {
90 // If there are no children, we encountered a leaf node. Non-zero available
91 // state means that this node was already removed previously. Also, if the
92 // bounds don't contain the request, and assuming the tree was previously
93 // split during insertion, then this node is not the node we're searching
94 // for.
95 if (mAvailable > 0 || !mBounds.Contains(aBounds)) {
96 return false;
98 // The bounds match exactly and it was previously inserted, so in this case
99 // we can just remove it.
100 if (mBounds == aBounds) {
101 mAvailable = std::min(mBounds.width, mBounds.height);
102 return true;
104 // We need to split this leaf node so that it can exactly match the removed
105 // bounds. We know the leaf node at least contains the removed bounds, but
106 // needs to be subdivided until it has a child node that exactly matches.
107 // Choose the axis to split with the largest amount of excess space. Within
108 // that axis, choose the larger of the space before or after the subrect as
109 // the split point to the new children.
110 if (mBounds.width - aBounds.width > mBounds.height - aBounds.height) {
111 int split = aBounds.x - mBounds.x > mBounds.XMost() - aBounds.XMost()
112 ? aBounds.x
113 : aBounds.XMost();
114 mChildren.reset(new TexturePacker[2]{
115 TexturePacker(
116 IntRect(mBounds.x, mBounds.y, split - mBounds.x, mBounds.height),
117 false),
118 TexturePacker(IntRect(split, mBounds.y, mBounds.XMost() - split,
119 mBounds.height),
120 false)});
121 } else {
122 int split = aBounds.y - mBounds.y > mBounds.YMost() - aBounds.YMost()
123 ? aBounds.y
124 : aBounds.YMost();
125 mChildren.reset(new TexturePacker[2]{
126 TexturePacker(
127 IntRect(mBounds.x, mBounds.y, mBounds.width, split - mBounds.y),
128 false),
129 TexturePacker(
130 IntRect(mBounds.x, split, mBounds.width, mBounds.YMost() - split),
131 false)});
134 // We've encountered a branch node. Determine which of the two child nodes
135 // would possibly contain the removed bounds. We first check which axis the
136 // children were split on and then whether the removed bounds on that axis
137 // are past the start of the second child. Proceed to recurse into that
138 // child node for removal.
139 bool next = mChildren[0].mBounds.x < mChildren[1].mBounds.x
140 ? aBounds.x >= mChildren[1].mBounds.x
141 : aBounds.y >= mChildren[1].mBounds.y;
142 bool removed = mChildren[next ? 1 : 0].Remove(aBounds);
143 if (removed) {
144 if (mChildren[0].IsFullyAvailable() && mChildren[1].IsFullyAvailable()) {
145 DiscardChildren();
146 mAvailable = std::min(mBounds.width, mBounds.height);
147 } else {
148 mAvailable = std::max(mChildren[0].mAvailable, mChildren[1].mAvailable);
151 return removed;
154 BackingTexture::BackingTexture(const IntSize& aSize, SurfaceFormat aFormat,
155 const RefPtr<WebGLTextureJS>& aTexture)
156 : mSize(aSize), mFormat(aFormat), mTexture(aTexture) {}
158 SharedTexture::SharedTexture(const IntSize& aSize, SurfaceFormat aFormat,
159 const RefPtr<WebGLTextureJS>& aTexture)
160 : BackingTexture(aSize, aFormat, aTexture),
161 mPacker(IntRect(IntPoint(0, 0), aSize)) {}
163 SharedTextureHandle::SharedTextureHandle(const IntRect& aBounds,
164 SharedTexture* aTexture)
165 : mBounds(aBounds), mTexture(aTexture) {}
167 already_AddRefed<SharedTextureHandle> SharedTexture::Allocate(
168 const IntSize& aSize) {
169 RefPtr<SharedTextureHandle> handle;
170 if (Maybe<IntPoint> origin = mPacker.Insert(aSize)) {
171 handle = new SharedTextureHandle(IntRect(*origin, aSize), this);
172 ++mAllocatedHandles;
174 return handle.forget();
177 bool SharedTexture::Free(const SharedTextureHandle& aHandle) {
178 if (aHandle.mTexture != this) {
179 return false;
181 if (!mPacker.Remove(aHandle.mBounds)) {
182 return false;
184 --mAllocatedHandles;
185 return true;
188 StandaloneTexture::StandaloneTexture(const IntSize& aSize,
189 SurfaceFormat aFormat,
190 const RefPtr<WebGLTextureJS>& aTexture)
191 : BackingTexture(aSize, aFormat, aTexture) {}
193 static Atomic<int64_t> sDrawTargetWebglCount(0);
195 DrawTargetWebgl::DrawTargetWebgl() { sDrawTargetWebglCount++; }
197 inline void DrawTargetWebgl::SharedContext::ClearLastTexture(bool aFullClear) {
198 mLastTexture = nullptr;
199 if (aFullClear) {
200 mLastClipMask = nullptr;
204 // Attempts to clear the snapshot state. If the snapshot is only referenced by
205 // this target, then it should simply be destroyed. If it is a WebGL surface in
206 // use by something else, then special cleanup such as reusing the texture or
207 // copy-on-write may be possible.
208 void DrawTargetWebgl::ClearSnapshot(bool aCopyOnWrite, bool aNeedHandle) {
209 if (!mSnapshot) {
210 return;
212 mSharedContext->ClearLastTexture();
213 if (mSnapshot->hasOneRef() || mSnapshot->GetType() != SurfaceType::WEBGL) {
214 mSnapshot = nullptr;
215 return;
217 RefPtr<SourceSurfaceWebgl> snapshot =
218 mSnapshot.forget().downcast<SourceSurfaceWebgl>();
219 if (aCopyOnWrite) {
220 // WebGL snapshots must be notified that the framebuffer contents will be
221 // changing so that it can copy the data.
222 snapshot->DrawTargetWillChange(aNeedHandle);
223 } else {
224 // If not copying, then give the backing texture to the surface for reuse.
225 snapshot->GiveTexture(
226 mSharedContext->WrapSnapshot(GetSize(), GetFormat(), mTex.forget()));
230 DrawTargetWebgl::~DrawTargetWebgl() {
231 sDrawTargetWebglCount--;
232 ClearSnapshot(false);
233 if (mSharedContext) {
234 if (mShmem.IsWritable()) {
235 // Force any Skia snapshots to copy the shmem before it deallocs.
236 mSkia->DetachAllSnapshots();
237 // Ensure we're done using the shmem before dealloc.
238 mSharedContext->WaitForShmem(this);
239 auto* child = mSharedContext->mWebgl->GetChild();
240 if (child && child->CanSend()) {
241 child->DeallocShmem(mShmem);
244 mSharedContext->ClearLastTexture(true);
245 if (mClipMask) {
246 mSharedContext->mWebgl->DeleteTexture(mClipMask);
248 if (mFramebuffer) {
249 mSharedContext->mWebgl->DeleteFramebuffer(mFramebuffer);
251 if (mTex) {
252 mSharedContext->mWebgl->DeleteTexture(mTex);
254 mSharedContext->mWebgl->Flush(false);
258 DrawTargetWebgl::SharedContext::SharedContext() = default;
260 DrawTargetWebgl::SharedContext::~SharedContext() {
261 if (sSharedContext.init() && sSharedContext.get() == this) {
262 sSharedContext.set(nullptr);
264 // Detect context loss before deletion.
265 if (mWebgl) {
266 mWebgl->ActiveTexture(LOCAL_GL_TEXTURE0);
268 ClearAllTextures();
269 UnlinkSurfaceTextures();
270 UnlinkGlyphCaches();
273 // Remove any SourceSurface user data associated with this TextureHandle.
274 inline void DrawTargetWebgl::SharedContext::UnlinkSurfaceTexture(
275 const RefPtr<TextureHandle>& aHandle) {
276 if (SourceSurface* surface = aHandle->GetSurface()) {
277 // Ensure any WebGL snapshot textures get unlinked.
278 if (surface->GetType() == SurfaceType::WEBGL) {
279 static_cast<SourceSurfaceWebgl*>(surface)->OnUnlinkTexture(this);
281 surface->RemoveUserData(aHandle->IsShadow() ? &mShadowTextureKey
282 : &mTextureHandleKey);
286 // Unlinks TextureHandles from any SourceSurface user data.
287 void DrawTargetWebgl::SharedContext::UnlinkSurfaceTextures() {
288 for (RefPtr<TextureHandle> handle = mTextureHandles.getFirst(); handle;
289 handle = handle->getNext()) {
290 UnlinkSurfaceTexture(handle);
294 // Unlinks GlyphCaches from any ScaledFont user data.
295 void DrawTargetWebgl::SharedContext::UnlinkGlyphCaches() {
296 GlyphCache* cache = mGlyphCaches.getFirst();
297 while (cache) {
298 ScaledFont* font = cache->GetFont();
299 // Access the next cache before removing the user data, as it might destroy
300 // the cache.
301 cache = cache->getNext();
302 font->RemoveUserData(&mGlyphCacheKey);
306 void DrawTargetWebgl::SharedContext::OnMemoryPressure() {
307 mShouldClearCaches = true;
310 // Clear out the entire list of texture handles from any source.
311 void DrawTargetWebgl::SharedContext::ClearAllTextures() {
312 while (!mTextureHandles.isEmpty()) {
313 PruneTextureHandle(mTextureHandles.popLast());
314 --mNumTextureHandles;
318 // Scan through the shared texture pages looking for any that are empty and
319 // delete them.
320 void DrawTargetWebgl::SharedContext::ClearEmptyTextureMemory() {
321 for (auto pos = mSharedTextures.begin(); pos != mSharedTextures.end();) {
322 if (!(*pos)->HasAllocatedHandles()) {
323 RefPtr<SharedTexture> shared = *pos;
324 size_t usedBytes = shared->UsedBytes();
325 mEmptyTextureMemory -= usedBytes;
326 mTotalTextureMemory -= usedBytes;
327 pos = mSharedTextures.erase(pos);
328 mWebgl->DeleteTexture(shared->GetWebGLTexture());
329 } else {
330 ++pos;
335 // If there is a request to clear out the caches because of memory pressure,
336 // then first clear out all the texture handles in the texture cache. If there
337 // are still empty texture pages being kept around, then clear those too.
338 void DrawTargetWebgl::SharedContext::ClearCachesIfNecessary() {
339 if (!mShouldClearCaches.exchange(false)) {
340 return;
342 mZeroBuffer = nullptr;
343 ClearAllTextures();
344 if (mEmptyTextureMemory) {
345 ClearEmptyTextureMemory();
347 ClearLastTexture();
350 // If a non-recoverable error occurred that would stop the canvas from initing.
351 static Atomic<bool> sContextInitError(false);
353 MOZ_THREAD_LOCAL(DrawTargetWebgl::SharedContext*)
354 DrawTargetWebgl::sSharedContext;
356 RefPtr<DrawTargetWebgl::SharedContext> DrawTargetWebgl::sMainSharedContext;
358 // Try to initialize a new WebGL context. Verifies that the requested size does
359 // not exceed the available texture limits and that shader creation succeeded.
360 bool DrawTargetWebgl::Init(const IntSize& size, const SurfaceFormat format) {
361 MOZ_ASSERT(format == SurfaceFormat::B8G8R8A8 ||
362 format == SurfaceFormat::B8G8R8X8);
364 mSize = size;
365 mFormat = format;
367 if (!sSharedContext.init()) {
368 return false;
371 DrawTargetWebgl::SharedContext* sharedContext = sSharedContext.get();
372 if (!sharedContext || sharedContext->IsContextLost()) {
373 mSharedContext = new DrawTargetWebgl::SharedContext;
374 if (!mSharedContext->Initialize()) {
375 mSharedContext = nullptr;
376 return false;
379 sSharedContext.set(mSharedContext.get());
381 if (NS_IsMainThread()) {
382 // Keep the shared context alive for the main thread by adding a ref.
383 // Ensure the ref will get cleared on shutdown so it doesn't leak.
384 if (!sMainSharedContext) {
385 ClearOnShutdown(&sMainSharedContext);
387 sMainSharedContext = mSharedContext;
389 } else {
390 mSharedContext = sharedContext;
393 if (size_t(std::max(size.width, size.height)) >
394 mSharedContext->mMaxTextureSize) {
395 return false;
398 if (!CreateFramebuffer()) {
399 return false;
402 auto* child = mSharedContext->mWebgl->GetChild();
403 if (child && child->CanSend()) {
404 size_t byteSize = layers::ImageDataSerializer::ComputeRGBBufferSize(
405 mSize, SurfaceFormat::B8G8R8A8);
406 if (byteSize) {
407 (void)child->AllocUnsafeShmem(byteSize, &mShmem);
410 mSkia = new DrawTargetSkia;
411 if (mShmem.IsWritable()) {
412 auto stride = layers::ImageDataSerializer::ComputeRGBStride(
413 SurfaceFormat::B8G8R8A8, size.width);
414 if (!mSkia->Init(mShmem.get<uint8_t>(), size, stride,
415 SurfaceFormat::B8G8R8A8, true)) {
416 return false;
418 } else if (!mSkia->Init(size, SurfaceFormat::B8G8R8A8)) {
419 return false;
422 // Allocate an unclipped copy of the DT pointing to its data.
423 uint8_t* dtData = nullptr;
424 IntSize dtSize;
425 int32_t dtStride = 0;
426 SurfaceFormat dtFormat = SurfaceFormat::UNKNOWN;
427 if (!mSkia->LockBits(&dtData, &dtSize, &dtStride, &dtFormat)) {
428 return false;
430 mSkiaNoClip = new DrawTargetSkia;
431 if (!mSkiaNoClip->Init(dtData, dtSize, dtStride, dtFormat, true)) {
432 mSkia->ReleaseBits(dtData);
433 return false;
435 mSkia->ReleaseBits(dtData);
437 SetPermitSubpixelAA(IsOpaque(format));
438 return true;
441 bool DrawTargetWebgl::SharedContext::Initialize() {
442 WebGLContextOptions options = {};
443 options.alpha = true;
444 options.depth = false;
445 options.stencil = false;
446 options.antialias = false;
447 options.preserveDrawingBuffer = true;
448 options.failIfMajorPerformanceCaveat = true;
450 mWebgl = new ClientWebGLContext(true);
451 mWebgl->SetContextOptions(options);
452 if (mWebgl->SetDimensions(1, 1) != NS_OK) {
453 // There was a non-recoverable error when trying to create a host context.
454 sContextInitError = true;
455 mWebgl = nullptr;
456 return false;
458 if (mWebgl->IsContextLost()) {
459 mWebgl = nullptr;
460 return false;
463 mMaxTextureSize = mWebgl->Limits().maxTex2dSize;
465 if (kIsMacOS) {
466 mRasterizationTruncates = mWebgl->Vendor() == gl::GLVendor::ATI;
469 CachePrefs();
471 if (!CreateShaders()) {
472 // There was a non-recoverable error when trying to init shaders.
473 sContextInitError = true;
474 mWebgl = nullptr;
475 return false;
478 return true;
481 void DrawTargetWebgl::SharedContext::SetBlendState(
482 CompositionOp aOp, const Maybe<DeviceColor>& aColor) {
483 if (aOp == mLastCompositionOp && mLastBlendColor == aColor) {
484 return;
486 mLastCompositionOp = aOp;
487 mLastBlendColor = aColor;
488 // AA is not supported for all composition ops, so switching blend modes may
489 // cause a toggle in AA state. Certain ops such as OP_SOURCE require output
490 // alpha that is blended separately from AA coverage. This would require two
491 // stage blending which can incur a substantial performance penalty, so to
492 // work around this currently we just disable AA for those ops.
494 // Map the composition op to a WebGL blend mode, if possible.
495 bool enabled = true;
496 switch (aOp) {
497 case CompositionOp::OP_OVER:
498 if (aColor) {
499 // If a color is supplied, then we blend subpixel text.
500 mWebgl->BlendColor(aColor->b, aColor->g, aColor->r, 1.0f);
501 mWebgl->BlendFunc(LOCAL_GL_CONSTANT_COLOR,
502 LOCAL_GL_ONE_MINUS_SRC_COLOR);
503 } else {
504 mWebgl->BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
506 break;
507 case CompositionOp::OP_ADD:
508 mWebgl->BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE);
509 break;
510 case CompositionOp::OP_ATOP:
511 mWebgl->BlendFunc(LOCAL_GL_DST_ALPHA, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
512 break;
513 case CompositionOp::OP_SOURCE:
514 if (aColor) {
515 // If a color is supplied, then we assume there is clipping or AA. This
516 // requires that we still use an over blend func with the clip/AA alpha,
517 // while filling the interior with the unaltered color. Normally this
518 // would require dual source blending, but we can emulate it with only
519 // a blend color.
520 mWebgl->BlendColor(aColor->b, aColor->g, aColor->r, aColor->a);
521 mWebgl->BlendFunc(LOCAL_GL_CONSTANT_COLOR,
522 LOCAL_GL_ONE_MINUS_SRC_COLOR);
523 } else {
524 enabled = false;
526 break;
527 case CompositionOp::OP_CLEAR:
528 // Assume the source is an alpha mask for clearing. Be careful to blend in
529 // the correct alpha if the target is opaque.
530 mWebgl->BlendFuncSeparate(
531 LOCAL_GL_ZERO, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
532 IsOpaque(mCurrentTarget->GetFormat()) ? LOCAL_GL_ONE : LOCAL_GL_ZERO,
533 LOCAL_GL_ONE_MINUS_SRC_ALPHA);
534 break;
535 default:
536 enabled = false;
537 break;
540 if (enabled) {
541 mWebgl->Enable(LOCAL_GL_BLEND);
542 } else {
543 mWebgl->Disable(LOCAL_GL_BLEND);
547 // Ensure the WebGL framebuffer is set to the current target.
548 bool DrawTargetWebgl::SharedContext::SetTarget(DrawTargetWebgl* aDT) {
549 if (!mWebgl || mWebgl->IsContextLost()) {
550 return false;
552 if (aDT != mCurrentTarget) {
553 mCurrentTarget = aDT;
554 if (aDT) {
555 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, aDT->mFramebuffer);
556 mViewportSize = aDT->GetSize();
557 mWebgl->Viewport(0, 0, mViewportSize.width, mViewportSize.height);
560 return true;
563 // Replace the current clip rect with a new potentially-AA'd clip rect.
564 void DrawTargetWebgl::SharedContext::SetClipRect(const Rect& aClipRect) {
565 // Only invalidate the clip rect if it actually changes.
566 if (!mClipAARect.IsEqualEdges(aClipRect)) {
567 mClipAARect = aClipRect;
568 // Store the integer-aligned bounds.
569 mClipRect = RoundedOut(aClipRect);
573 bool DrawTargetWebgl::SharedContext::SetClipMask(
574 const RefPtr<WebGLTextureJS>& aTex) {
575 if (mLastClipMask != aTex) {
576 if (!mWebgl) {
577 return false;
579 mWebgl->ActiveTexture(LOCAL_GL_TEXTURE1);
580 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, aTex);
581 mWebgl->ActiveTexture(LOCAL_GL_TEXTURE0);
582 mLastClipMask = aTex;
584 return true;
587 bool DrawTargetWebgl::SharedContext::SetNoClipMask() {
588 if (mNoClipMask) {
589 return SetClipMask(mNoClipMask);
591 if (!mWebgl) {
592 return false;
594 mNoClipMask = mWebgl->CreateTexture();
595 if (!mNoClipMask) {
596 return false;
598 mWebgl->ActiveTexture(LOCAL_GL_TEXTURE1);
599 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mNoClipMask);
600 static const uint8_t solidMask[4] = {0xFF, 0xFF, 0xFF, 0xFF};
601 mWebgl->RawTexImage(
602 0, LOCAL_GL_RGBA8, {0, 0, 0}, {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE},
603 {LOCAL_GL_TEXTURE_2D,
604 {1, 1, 1},
605 gfxAlphaType::NonPremult,
606 Some(RawBuffer(Range<const uint8_t>(solidMask, sizeof(solidMask))))});
607 InitTexParameters(mNoClipMask, false);
608 mWebgl->ActiveTexture(LOCAL_GL_TEXTURE0);
609 mLastClipMask = mNoClipMask;
610 return true;
613 inline bool DrawTargetWebgl::ClipStack::operator==(
614 const DrawTargetWebgl::ClipStack& aOther) const {
615 // Verify the transform and bounds match.
616 if (!mTransform.FuzzyEquals(aOther.mTransform) ||
617 !mRect.IsEqualInterior(aOther.mRect)) {
618 return false;
620 // Verify the paths match.
621 if (!mPath) {
622 return !aOther.mPath;
624 if (!aOther.mPath ||
625 mPath->GetBackendType() != aOther.mPath->GetBackendType()) {
626 return false;
628 if (mPath->GetBackendType() != BackendType::SKIA) {
629 return mPath == aOther.mPath;
631 return static_cast<const PathSkia*>(mPath.get())->GetPath() ==
632 static_cast<const PathSkia*>(aOther.mPath.get())->GetPath();
635 // If the clip region can't be approximated by a simple clip rect, then we need
636 // to generate a clip mask that can represent the clip region per-pixel. We
637 // render to the Skia target temporarily, transparent outside the clip region,
638 // opaque inside, and upload this to a texture that can be used by the shaders.
639 bool DrawTargetWebgl::GenerateComplexClipMask() {
640 if (!mClipChanged || (mClipMask && mCachedClipStack == mClipStack)) {
641 mClipChanged = false;
642 // If the clip mask was already generated, use the cached mask and bounds.
643 mSharedContext->SetClipMask(mClipMask);
644 mSharedContext->SetClipRect(mClipBounds);
645 return true;
647 if (!mWebglValid) {
648 // If the Skia target is currently being used, then we can't render the mask
649 // in it.
650 return false;
652 RefPtr<ClientWebGLContext> webgl = mSharedContext->mWebgl;
653 if (!webgl) {
654 return false;
656 bool init = false;
657 if (!mClipMask) {
658 mClipMask = webgl->CreateTexture();
659 if (!mClipMask) {
660 return false;
662 init = true;
664 // Try to get the bounds of the clip to limit the size of the mask.
665 if (Maybe<IntRect> clip = mSkia->GetDeviceClipRect(true)) {
666 mClipBounds = *clip;
667 } else {
668 // If we can't get bounds, then just use the entire viewport.
669 mClipBounds = GetRect();
671 mClipAARect = Rect(mClipBounds);
672 // If initializing the clip mask, then allocate the entire texture to ensure
673 // all pixels get filled with an empty mask regardless. Otherwise, restrict
674 // uploading to only the clip region.
675 RefPtr<DrawTargetSkia> dt = new DrawTargetSkia;
676 if (!dt->Init(mClipBounds.Size(), SurfaceFormat::A8)) {
677 return false;
679 // Set the clip region and fill the entire inside of it
680 // with opaque white.
681 mCachedClipStack.clear();
682 for (auto& clipStack : mClipStack) {
683 // Record the current state of the clip stack for this mask.
684 mCachedClipStack.push_back(clipStack);
685 dt->SetTransform(
686 Matrix(clipStack.mTransform).PostTranslate(-mClipBounds.TopLeft()));
687 if (clipStack.mPath) {
688 dt->PushClip(clipStack.mPath);
689 } else {
690 dt->PushClipRect(clipStack.mRect);
693 dt->SetTransform(Matrix::Translation(-mClipBounds.TopLeft()));
694 dt->FillRect(Rect(mClipBounds), ColorPattern(DeviceColor(1, 1, 1, 1)));
695 // Bind the clip mask for uploading.
696 webgl->ActiveTexture(LOCAL_GL_TEXTURE1);
697 webgl->BindTexture(LOCAL_GL_TEXTURE_2D, mClipMask);
698 if (init) {
699 mSharedContext->InitTexParameters(mClipMask, false);
701 RefPtr<DataSourceSurface> data;
702 if (RefPtr<SourceSurface> snapshot = dt->Snapshot()) {
703 data = snapshot->GetDataSurface();
705 // Finally, upload the texture data and initialize texture storage if
706 // necessary.
707 if (init && mClipBounds.Size() != mSize) {
708 mSharedContext->UploadSurface(nullptr, SurfaceFormat::A8, GetRect(),
709 IntPoint(), true, true);
710 init = false;
712 mSharedContext->UploadSurface(data, SurfaceFormat::A8,
713 IntRect(IntPoint(), mClipBounds.Size()),
714 mClipBounds.TopLeft(), init);
715 webgl->ActiveTexture(LOCAL_GL_TEXTURE0);
716 // We already bound the texture, so notify the shared context that the clip
717 // mask changed to it.
718 mSharedContext->mLastClipMask = mClipMask;
719 mSharedContext->SetClipRect(mClipBounds);
720 // We uploaded a surface, just as if we missed the texture cache, so account
721 // for that here.
722 mProfile.OnCacheMiss();
723 return !!data;
726 bool DrawTargetWebgl::SetSimpleClipRect() {
727 // Determine whether the clipping rectangle is simple enough to accelerate.
728 // Check if there is a device space clip rectangle available from the Skia
729 // target.
730 if (Maybe<IntRect> clip = mSkia->GetDeviceClipRect(false)) {
731 // If the clip is empty, leave the final integer clip rectangle empty to
732 // trivially discard the draw request.
733 // If the clip rect is larger than the viewport, just set it to the
734 // viewport.
735 if (!clip->IsEmpty() && clip->Contains(GetRect())) {
736 clip = Some(GetRect());
738 mSharedContext->SetClipRect(*clip);
739 mSharedContext->SetNoClipMask();
740 return true;
743 // There was no pixel-aligned clip rect available, so check the clip stack to
744 // see if there is an AA'd axis-aligned rectangle clip.
745 Rect rect(GetRect());
746 for (auto& clipStack : mClipStack) {
747 // If clip is a path or it has a non-axis-aligned transform, then it is
748 // complex.
749 if (clipStack.mPath ||
750 !clipStack.mTransform.PreservesAxisAlignedRectangles()) {
751 return false;
753 // Transform the rect and intersect it with the current clip.
754 rect =
755 clipStack.mTransform.TransformBounds(clipStack.mRect).Intersect(rect);
757 mSharedContext->SetClipRect(rect);
758 mSharedContext->SetNoClipMask();
759 return true;
762 // Installs the Skia clip rectangle, if applicable, onto the shared WebGL
763 // context as well as sets the WebGL framebuffer to the current target.
764 bool DrawTargetWebgl::PrepareContext(bool aClipped) {
765 if (!aClipped) {
766 // If no clipping requested, just set the clip rect to the viewport.
767 mSharedContext->SetClipRect(GetRect());
768 mSharedContext->SetNoClipMask();
769 // Ensure the clip gets reset if clipping is later requested for the target.
770 mRefreshClipState = true;
771 } else if (mRefreshClipState || !mSharedContext->IsCurrentTarget(this)) {
772 // Try to use a simple clip rect if possible. Otherwise, fall back to
773 // generating a clip mask texture that can represent complex clip regions.
774 if (!SetSimpleClipRect() && !GenerateComplexClipMask()) {
775 return false;
777 mClipChanged = false;
778 mRefreshClipState = false;
780 return mSharedContext->SetTarget(this);
783 bool DrawTargetWebgl::SharedContext::IsContextLost() const {
784 return !mWebgl || mWebgl->IsContextLost();
787 // Signal to CanvasRenderingContext2D when the WebGL context is lost.
788 bool DrawTargetWebgl::IsValid() const {
789 return mSharedContext && !mSharedContext->IsContextLost();
792 already_AddRefed<DrawTargetWebgl> DrawTargetWebgl::Create(
793 const IntSize& aSize, SurfaceFormat aFormat) {
794 if (!gfxVars::UseAcceleratedCanvas2D()) {
795 return nullptr;
798 int64_t count = sDrawTargetWebglCount;
799 if (count > StaticPrefs::gfx_canvas_accelerated_max_draw_target_count()) {
800 return nullptr;
803 // If context initialization would fail, don't even try to create a context.
804 if (sContextInitError) {
805 return nullptr;
808 if (!Factory::AllowedSurfaceSize(aSize)) {
809 return nullptr;
812 // The interpretation of the min-size and max-size follows from the old
813 // SkiaGL prefs. First just ensure that the context is not unreasonably
814 // small.
815 static const int32_t kMinDimension = 16;
816 if (std::min(aSize.width, aSize.height) < kMinDimension) {
817 return nullptr;
820 int32_t minSize = StaticPrefs::gfx_canvas_accelerated_min_size();
821 if (aSize.width * aSize.height < minSize * minSize) {
822 return nullptr;
825 // Maximum pref allows 3 different options:
826 // 0 means unlimited size,
827 // > 0 means use value as an absolute threshold,
828 // < 0 means use the number of screen pixels as a threshold.
829 int32_t maxSize = StaticPrefs::gfx_canvas_accelerated_max_size();
830 if (maxSize > 0) {
831 if (std::max(aSize.width, aSize.height) > maxSize) {
832 return nullptr;
834 } else if (maxSize < 0) {
835 // Default to historical mobile screen size of 980x480, like FishIEtank.
836 // In addition, allow acceleration up to this size even if the screen is
837 // smaller. A lot content expects this size to work well. See Bug 999841
838 static const int32_t kScreenPixels = 980 * 480;
839 IntSize screenSize = gfxPlatform::GetPlatform()->GetScreenSize();
840 if (aSize.width * aSize.height >
841 std::max(screenSize.width * screenSize.height, kScreenPixels)) {
842 return nullptr;
846 RefPtr<DrawTargetWebgl> dt = new DrawTargetWebgl;
847 if (!dt->Init(aSize, aFormat) || !dt->IsValid()) {
848 return nullptr;
851 return dt.forget();
854 void* DrawTargetWebgl::GetNativeSurface(NativeSurfaceType aType) {
855 switch (aType) {
856 case NativeSurfaceType::WEBGL_CONTEXT:
857 // If the context is lost, then don't attempt to access it.
858 if (mSharedContext->IsContextLost()) {
859 return nullptr;
861 if (!mWebglValid) {
862 FlushFromSkia();
864 return mSharedContext->mWebgl.get();
865 default:
866 return nullptr;
870 // Wrap a WebGL texture holding a snapshot with a texture handle. Note that
871 // while the texture is still in use as the backing texture of a framebuffer,
872 // it's texture memory is not currently tracked with other texture handles.
873 // Once it is finally orphaned and used as a texture handle, it must be added
874 // to the resource usage totals.
875 already_AddRefed<TextureHandle> DrawTargetWebgl::SharedContext::WrapSnapshot(
876 const IntSize& aSize, SurfaceFormat aFormat, RefPtr<WebGLTextureJS> aTex) {
877 // Ensure there is enough space for the texture.
878 size_t usedBytes = BackingTexture::UsedBytes(aFormat, aSize);
879 PruneTextureMemory(usedBytes, false);
880 // Allocate a handle for the texture
881 RefPtr<StandaloneTexture> handle =
882 new StandaloneTexture(aSize, aFormat, aTex.forget());
883 mStandaloneTextures.push_back(handle);
884 mTextureHandles.insertFront(handle);
885 mTotalTextureMemory += usedBytes;
886 mUsedTextureMemory += usedBytes;
887 ++mNumTextureHandles;
888 return handle.forget();
891 void DrawTargetWebgl::SharedContext::SetTexFilter(WebGLTextureJS* aTex,
892 bool aFilter) {
893 mWebgl->TexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
894 aFilter ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST);
895 mWebgl->TexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
896 aFilter ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST);
899 void DrawTargetWebgl::SharedContext::InitTexParameters(WebGLTextureJS* aTex,
900 bool aFilter) {
901 mWebgl->TexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
902 LOCAL_GL_CLAMP_TO_EDGE);
903 mWebgl->TexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
904 LOCAL_GL_CLAMP_TO_EDGE);
905 SetTexFilter(aTex, aFilter);
908 // Copy the contents of the WebGL framebuffer into a WebGL texture.
909 already_AddRefed<TextureHandle> DrawTargetWebgl::SharedContext::CopySnapshot(
910 const IntRect& aRect, TextureHandle* aHandle) {
911 if (!mWebgl || mWebgl->IsContextLost()) {
912 return nullptr;
915 // If the target is going away, then we can just directly reuse the
916 // framebuffer texture since it will never change.
917 RefPtr<WebGLTextureJS> tex = mWebgl->CreateTexture();
918 if (!tex) {
919 return nullptr;
922 // If copying from a non-DT source, we have to bind a scratch framebuffer for
923 // reading.
924 if (aHandle) {
925 if (!mScratchFramebuffer) {
926 mScratchFramebuffer = mWebgl->CreateFramebuffer();
928 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mScratchFramebuffer);
929 mWebgl->FramebufferTexture2D(
930 LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D,
931 aHandle->GetBackingTexture()->GetWebGLTexture(), 0);
934 // Create a texture to hold the copy
935 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex);
936 mWebgl->TexStorage2D(LOCAL_GL_TEXTURE_2D, 1, LOCAL_GL_RGBA8, aRect.width,
937 aRect.height);
938 InitTexParameters(tex);
939 // Copy the framebuffer into the texture
940 mWebgl->CopyTexSubImage2D(LOCAL_GL_TEXTURE_2D, 0, 0, 0, aRect.x, aRect.y,
941 aRect.width, aRect.height);
942 ClearLastTexture();
944 SurfaceFormat format =
945 aHandle ? aHandle->GetFormat() : mCurrentTarget->GetFormat();
946 already_AddRefed<TextureHandle> result =
947 WrapSnapshot(aRect.Size(), format, tex.forget());
949 // Restore the actual framebuffer after reading is done.
950 if (aHandle && mCurrentTarget) {
951 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mCurrentTarget->mFramebuffer);
954 return result;
957 inline DrawTargetWebgl::AutoRestoreContext::AutoRestoreContext(
958 DrawTargetWebgl* aTarget)
959 : mTarget(aTarget),
960 mClipAARect(aTarget->mSharedContext->mClipAARect),
961 mLastClipMask(aTarget->mSharedContext->mLastClipMask) {}
963 inline DrawTargetWebgl::AutoRestoreContext::~AutoRestoreContext() {
964 mTarget->mSharedContext->SetClipRect(mClipAARect);
965 if (mLastClipMask) {
966 mTarget->mSharedContext->SetClipMask(mLastClipMask);
968 mTarget->mRefreshClipState = true;
971 // Utility method to install the target before copying a snapshot.
972 already_AddRefed<TextureHandle> DrawTargetWebgl::CopySnapshot(
973 const IntRect& aRect) {
974 AutoRestoreContext restore(this);
975 if (!PrepareContext(false)) {
976 return nullptr;
978 return mSharedContext->CopySnapshot(aRect);
981 // Borrow a snapshot that may be used by another thread for composition. Only
982 // Skia snapshots are safe to pass around.
983 already_AddRefed<SourceSurface> DrawTargetWebgl::GetDataSnapshot() {
984 if (!mSkiaValid) {
985 ReadIntoSkia();
986 } else if (mSkiaLayer) {
987 FlattenSkia();
989 return mSkia->Snapshot(mFormat);
992 already_AddRefed<SourceSurface> DrawTargetWebgl::Snapshot() {
993 // If already using the Skia fallback, then just snapshot that.
994 if (mSkiaValid) {
995 return GetDataSnapshot();
998 // There's no valid Skia snapshot, so we need to get one from the WebGL
999 // context.
1000 if (!mSnapshot) {
1001 // Create a copy-on-write reference to this target.
1002 mSnapshot = new SourceSurfaceWebgl(this);
1004 return do_AddRef(mSnapshot);
1007 // If we need to provide a snapshot for another DrawTargetWebgl that shares the
1008 // same WebGL context, then it is safe to directly return a snapshot. Otherwise,
1009 // we may be exporting to another thread and require a data snapshot.
1010 already_AddRefed<SourceSurface> DrawTargetWebgl::GetOptimizedSnapshot(
1011 DrawTarget* aTarget) {
1012 if (aTarget && aTarget->GetBackendType() == BackendType::WEBGL &&
1013 static_cast<DrawTargetWebgl*>(aTarget)->mSharedContext ==
1014 mSharedContext) {
1015 return Snapshot();
1017 return GetDataSnapshot();
1020 // Read from the WebGL context into a buffer. This handles both swizzling BGRA
1021 // to RGBA and flipping the image.
1022 bool DrawTargetWebgl::SharedContext::ReadInto(uint8_t* aDstData,
1023 int32_t aDstStride,
1024 SurfaceFormat aFormat,
1025 const IntRect& aBounds,
1026 TextureHandle* aHandle) {
1027 MOZ_ASSERT(aFormat == SurfaceFormat::B8G8R8A8 ||
1028 aFormat == SurfaceFormat::B8G8R8X8);
1030 // If reading into a new texture, we have to bind it to a scratch framebuffer
1031 // for reading.
1032 if (aHandle) {
1033 if (!mScratchFramebuffer) {
1034 mScratchFramebuffer = mWebgl->CreateFramebuffer();
1036 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mScratchFramebuffer);
1037 mWebgl->FramebufferTexture2D(
1038 LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D,
1039 aHandle->GetBackingTexture()->GetWebGLTexture(), 0);
1040 } else if (mCurrentTarget && mCurrentTarget->mIsClear) {
1041 // If reading from a target that is still clear, then avoid the readback by
1042 // just clearing the data.
1043 SkPixmap(MakeSkiaImageInfo(aBounds.Size(), aFormat), aDstData, aDstStride)
1044 .erase(IsOpaque(aFormat) ? SK_ColorBLACK : SK_ColorTRANSPARENT);
1045 return true;
1048 webgl::ReadPixelsDesc desc;
1049 desc.srcOffset = *ivec2::From(aBounds);
1050 desc.size = *uvec2::FromSize(aBounds);
1051 desc.packState.rowLength = aDstStride / 4;
1053 bool success = false;
1054 if (mCurrentTarget && mCurrentTarget->mShmem.IsWritable() &&
1055 aDstData == mCurrentTarget->mShmem.get<uint8_t>()) {
1056 success = mWebgl->DoReadPixels(desc, mCurrentTarget->mShmem);
1057 } else {
1058 Range<uint8_t> range = {aDstData, size_t(aDstStride) * aBounds.height};
1059 success = mWebgl->DoReadPixels(desc, range);
1062 // Restore the actual framebuffer after reading is done.
1063 if (aHandle && mCurrentTarget) {
1064 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mCurrentTarget->mFramebuffer);
1067 return success;
1070 already_AddRefed<DataSourceSurface>
1071 DrawTargetWebgl::SharedContext::ReadSnapshot(TextureHandle* aHandle) {
1072 // Allocate a data surface, map it, and read from the WebGL context into the
1073 // surface.
1074 SurfaceFormat format = SurfaceFormat::UNKNOWN;
1075 IntRect bounds;
1076 if (aHandle) {
1077 format = aHandle->GetFormat();
1078 bounds = aHandle->GetBounds();
1079 } else {
1080 format = mCurrentTarget->GetFormat();
1081 bounds = mCurrentTarget->GetRect();
1083 RefPtr<DataSourceSurface> surface =
1084 Factory::CreateDataSourceSurface(bounds.Size(), format);
1085 if (!surface) {
1086 return nullptr;
1088 DataSourceSurface::ScopedMap dstMap(surface, DataSourceSurface::WRITE);
1089 if (!dstMap.IsMapped() || !ReadInto(dstMap.GetData(), dstMap.GetStride(),
1090 format, bounds, aHandle)) {
1091 return nullptr;
1093 return surface.forget();
1096 // Utility method to install the target before reading a snapshot.
1097 bool DrawTargetWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride) {
1098 if (!PrepareContext(false)) {
1099 return false;
1102 return mSharedContext->ReadInto(aDstData, aDstStride, GetFormat(), GetRect());
1105 // Utility method to install the target before reading a snapshot.
1106 already_AddRefed<DataSourceSurface> DrawTargetWebgl::ReadSnapshot() {
1107 AutoRestoreContext restore(this);
1108 if (!PrepareContext(false)) {
1109 return nullptr;
1111 mProfile.OnReadback();
1112 return mSharedContext->ReadSnapshot();
1115 already_AddRefed<SourceSurface> DrawTargetWebgl::GetBackingSurface() {
1116 return Snapshot();
1119 void DrawTargetWebgl::DetachAllSnapshots() {
1120 mSkia->DetachAllSnapshots();
1121 ClearSnapshot();
1124 // Prepare the framebuffer for accelerated drawing. Any cached snapshots will
1125 // be invalidated if not detached and copied here. Ensure the WebGL
1126 // framebuffer's contents are updated if still somehow stored in the Skia
1127 // framebuffer.
1128 bool DrawTargetWebgl::MarkChanged() {
1129 if (mSnapshot) {
1130 // Try to copy the target into a new texture if possible.
1131 ClearSnapshot(true, true);
1133 if (!mWebglValid && !FlushFromSkia()) {
1134 return false;
1136 mSkiaValid = false;
1137 mIsClear = false;
1138 return true;
1141 bool DrawTargetWebgl::LockBits(uint8_t** aData, IntSize* aSize,
1142 int32_t* aStride, SurfaceFormat* aFormat,
1143 IntPoint* aOrigin) {
1144 // Can only access pixels if there is valid, flattened Skia data.
1145 if (mSkiaValid && !mSkiaLayer) {
1146 MarkSkiaChanged();
1147 return mSkia->LockBits(aData, aSize, aStride, aFormat, aOrigin);
1149 return false;
1152 void DrawTargetWebgl::ReleaseBits(uint8_t* aData) {
1153 // Can only access pixels if there is valid, flattened Skia data.
1154 if (mSkiaValid && !mSkiaLayer) {
1155 mSkia->ReleaseBits(aData);
1159 // Format is x, y, alpha
1160 static const float kRectVertexData[12] = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
1161 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f};
1163 // Orphans the contents of the path vertex buffer. The beginning of the buffer
1164 // always contains data for a simple rectangle draw to avoid needing to switch
1165 // buffers.
1166 void DrawTargetWebgl::SharedContext::ResetPathVertexBuffer(bool aChanged) {
1167 mWebgl->BindBuffer(LOCAL_GL_ARRAY_BUFFER, mPathVertexBuffer.get());
1168 mWebgl->RawBufferData(
1169 LOCAL_GL_ARRAY_BUFFER, nullptr,
1170 std::max(size_t(mPathVertexCapacity), sizeof(kRectVertexData)),
1171 LOCAL_GL_DYNAMIC_DRAW);
1172 mWebgl->RawBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0,
1173 (const uint8_t*)kRectVertexData,
1174 sizeof(kRectVertexData));
1175 mPathVertexOffset = sizeof(kRectVertexData);
1176 if (aChanged) {
1177 mWGROutputBuffer.reset(
1178 mPathVertexCapacity > 0
1179 ? new (fallible) WGR::OutputVertex[mPathVertexCapacity /
1180 sizeof(WGR::OutputVertex)]
1181 : nullptr);
1185 // Attempts to create all shaders and resources to be used for drawing commands.
1186 // Returns whether or not this succeeded.
1187 bool DrawTargetWebgl::SharedContext::CreateShaders() {
1188 if (!mPathVertexArray) {
1189 mPathVertexArray = mWebgl->CreateVertexArray();
1191 if (!mPathVertexBuffer) {
1192 mPathVertexBuffer = mWebgl->CreateBuffer();
1193 mWebgl->BindVertexArray(mPathVertexArray.get());
1194 ResetPathVertexBuffer();
1195 mWebgl->EnableVertexAttribArray(0);
1196 mWebgl->VertexAttribPointer(0, 3, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, 0);
1198 if (!mSolidProgram) {
1199 // AA is computed by using the basis vectors of the transform to determine
1200 // both the scale and orientation. The scale is then used to extrude the
1201 // rectangle outward by 1 screen-space pixel to account for the AA region.
1202 // The distance to the rectangle edges is passed to the fragment shader in
1203 // an interpolant, biased by 0.5 so it represents the desired coverage. The
1204 // minimum coverage is then chosen by the fragment shader to use as an AA
1205 // coverage value to modulate the color.
1206 auto vsSource =
1207 u"attribute vec3 a_vertex;\n"
1208 "uniform vec2 u_transform[3];\n"
1209 "uniform vec2 u_viewport;\n"
1210 "uniform vec4 u_clipbounds;\n"
1211 "uniform float u_aa;\n"
1212 "varying vec2 v_cliptc;\n"
1213 "varying vec4 v_clipdist;\n"
1214 "varying vec4 v_dist;\n"
1215 "varying float v_alpha;\n"
1216 "void main() {\n"
1217 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1218 " dot(u_transform[1], u_transform[1]));\n"
1219 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1220 " scale *= invScale;\n"
1221 " vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
1222 "1.0);\n"
1223 " vec2 vertex = u_transform[0] * extrude.x +\n"
1224 " u_transform[1] * extrude.y +\n"
1225 " u_transform[2];\n"
1226 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1227 " v_cliptc = vertex / u_viewport;\n"
1228 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
1229 " u_clipbounds.zw - vertex);\n"
1230 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
1231 " v_alpha = a_vertex.z;\n"
1232 "}\n"_ns;
1233 auto fsSource =
1234 u"precision mediump float;\n"
1235 "uniform vec4 u_color;\n"
1236 "uniform sampler2D u_clipmask;\n"
1237 "varying highp vec2 v_cliptc;\n"
1238 "varying vec4 v_clipdist;\n"
1239 "varying vec4 v_dist;\n"
1240 "varying float v_alpha;\n"
1241 "void main() {\n"
1242 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1243 " vec4 dist = min(v_dist, v_clipdist);\n"
1244 " dist.xy = min(dist.xy, dist.zw);\n"
1245 " float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
1246 " gl_FragColor = clip * aa * u_color;\n"
1247 "}\n"_ns;
1248 RefPtr<WebGLShaderJS> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER);
1249 mWebgl->ShaderSource(*vsId, vsSource);
1250 mWebgl->CompileShader(*vsId);
1251 if (!mWebgl->GetCompileResult(*vsId).success) {
1252 return false;
1254 RefPtr<WebGLShaderJS> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER);
1255 mWebgl->ShaderSource(*fsId, fsSource);
1256 mWebgl->CompileShader(*fsId);
1257 if (!mWebgl->GetCompileResult(*fsId).success) {
1258 return false;
1260 mSolidProgram = mWebgl->CreateProgram();
1261 mWebgl->AttachShader(*mSolidProgram, *vsId);
1262 mWebgl->AttachShader(*mSolidProgram, *fsId);
1263 mWebgl->BindAttribLocation(*mSolidProgram, 0, u"a_vertex"_ns);
1264 mWebgl->LinkProgram(*mSolidProgram);
1265 if (!mWebgl->GetLinkResult(*mSolidProgram).success) {
1266 return false;
1268 mSolidProgramViewport =
1269 mWebgl->GetUniformLocation(*mSolidProgram, u"u_viewport"_ns);
1270 mSolidProgramAA = mWebgl->GetUniformLocation(*mSolidProgram, u"u_aa"_ns);
1271 mSolidProgramTransform =
1272 mWebgl->GetUniformLocation(*mSolidProgram, u"u_transform"_ns);
1273 mSolidProgramColor =
1274 mWebgl->GetUniformLocation(*mSolidProgram, u"u_color"_ns);
1275 mSolidProgramClipMask =
1276 mWebgl->GetUniformLocation(*mSolidProgram, u"u_clipmask"_ns);
1277 mSolidProgramClipBounds =
1278 mWebgl->GetUniformLocation(*mSolidProgram, u"u_clipbounds"_ns);
1279 if (!mSolidProgramViewport || !mSolidProgramAA || !mSolidProgramTransform ||
1280 !mSolidProgramColor || !mSolidProgramClipMask ||
1281 !mSolidProgramClipBounds) {
1282 return false;
1284 mWebgl->UseProgram(mSolidProgram);
1285 int32_t clipMaskData = 1;
1286 mWebgl->UniformData(LOCAL_GL_INT, mSolidProgramClipMask, false,
1287 {(const uint8_t*)&clipMaskData, sizeof(clipMaskData)});
1290 if (!mImageProgram) {
1291 auto vsSource =
1292 u"attribute vec3 a_vertex;\n"
1293 "uniform vec2 u_viewport;\n"
1294 "uniform vec4 u_clipbounds;\n"
1295 "uniform float u_aa;\n"
1296 "uniform vec2 u_transform[3];\n"
1297 "uniform vec2 u_texmatrix[3];\n"
1298 "varying vec2 v_cliptc;\n"
1299 "varying vec2 v_texcoord;\n"
1300 "varying vec4 v_clipdist;\n"
1301 "varying vec4 v_dist;\n"
1302 "varying float v_alpha;\n"
1303 "void main() {\n"
1304 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
1305 " dot(u_transform[1], u_transform[1]));\n"
1306 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
1307 " scale *= invScale;\n"
1308 " vec2 extrude = a_vertex.xy + invScale * (2.0 * a_vertex.xy - "
1309 "1.0);\n"
1310 " vec2 vertex = u_transform[0] * extrude.x +\n"
1311 " u_transform[1] * extrude.y +\n"
1312 " u_transform[2];\n"
1313 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
1314 " v_cliptc = vertex / u_viewport;\n"
1315 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
1316 " u_clipbounds.zw - vertex);\n"
1317 " v_texcoord = u_texmatrix[0] * extrude.x +\n"
1318 " u_texmatrix[1] * extrude.y +\n"
1319 " u_texmatrix[2];\n"
1320 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 1.5 - u_aa;\n"
1321 " v_alpha = a_vertex.z;\n"
1322 "}\n"_ns;
1323 auto fsSource =
1324 u"precision mediump float;\n"
1325 "uniform vec4 u_texbounds;\n"
1326 "uniform vec4 u_color;\n"
1327 "uniform float u_swizzle;\n"
1328 "uniform sampler2D u_sampler;\n"
1329 "uniform sampler2D u_clipmask;\n"
1330 "varying highp vec2 v_cliptc;\n"
1331 "varying highp vec2 v_texcoord;\n"
1332 "varying vec4 v_clipdist;\n"
1333 "varying vec4 v_dist;\n"
1334 "varying float v_alpha;\n"
1335 "void main() {\n"
1336 " highp vec2 tc = clamp(v_texcoord, u_texbounds.xy,\n"
1337 " u_texbounds.zw);\n"
1338 " vec4 image = texture2D(u_sampler, tc);\n"
1339 " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
1340 " vec4 dist = min(v_dist, v_clipdist);\n"
1341 " dist.xy = min(dist.xy, dist.zw);\n"
1342 " float aa = v_alpha * clamp(min(dist.x, dist.y), 0.0, 1.0);\n"
1343 " gl_FragColor = clip * aa * u_color *\n"
1344 " mix(image, image.rrrr, u_swizzle);\n"
1345 "}\n"_ns;
1346 RefPtr<WebGLShaderJS> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER);
1347 mWebgl->ShaderSource(*vsId, vsSource);
1348 mWebgl->CompileShader(*vsId);
1349 if (!mWebgl->GetCompileResult(*vsId).success) {
1350 return false;
1352 RefPtr<WebGLShaderJS> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER);
1353 mWebgl->ShaderSource(*fsId, fsSource);
1354 mWebgl->CompileShader(*fsId);
1355 if (!mWebgl->GetCompileResult(*fsId).success) {
1356 return false;
1358 mImageProgram = mWebgl->CreateProgram();
1359 mWebgl->AttachShader(*mImageProgram, *vsId);
1360 mWebgl->AttachShader(*mImageProgram, *fsId);
1361 mWebgl->BindAttribLocation(*mImageProgram, 0, u"a_vertex"_ns);
1362 mWebgl->LinkProgram(*mImageProgram);
1363 if (!mWebgl->GetLinkResult(*mImageProgram).success) {
1364 return false;
1366 mImageProgramViewport =
1367 mWebgl->GetUniformLocation(*mImageProgram, u"u_viewport"_ns);
1368 mImageProgramAA = mWebgl->GetUniformLocation(*mImageProgram, u"u_aa"_ns);
1369 mImageProgramTransform =
1370 mWebgl->GetUniformLocation(*mImageProgram, u"u_transform"_ns);
1371 mImageProgramTexMatrix =
1372 mWebgl->GetUniformLocation(*mImageProgram, u"u_texmatrix"_ns);
1373 mImageProgramTexBounds =
1374 mWebgl->GetUniformLocation(*mImageProgram, u"u_texbounds"_ns);
1375 mImageProgramSwizzle =
1376 mWebgl->GetUniformLocation(*mImageProgram, u"u_swizzle"_ns);
1377 mImageProgramColor =
1378 mWebgl->GetUniformLocation(*mImageProgram, u"u_color"_ns);
1379 mImageProgramSampler =
1380 mWebgl->GetUniformLocation(*mImageProgram, u"u_sampler"_ns);
1381 mImageProgramClipMask =
1382 mWebgl->GetUniformLocation(*mImageProgram, u"u_clipmask"_ns);
1383 mImageProgramClipBounds =
1384 mWebgl->GetUniformLocation(*mImageProgram, u"u_clipbounds"_ns);
1385 if (!mImageProgramViewport || !mImageProgramAA || !mImageProgramTransform ||
1386 !mImageProgramTexMatrix || !mImageProgramTexBounds ||
1387 !mImageProgramSwizzle || !mImageProgramColor || !mImageProgramSampler ||
1388 !mImageProgramClipMask || !mImageProgramClipBounds) {
1389 return false;
1391 mWebgl->UseProgram(mImageProgram);
1392 int32_t samplerData = 0;
1393 mWebgl->UniformData(LOCAL_GL_INT, mImageProgramSampler, false,
1394 {(const uint8_t*)&samplerData, sizeof(samplerData)});
1395 int32_t clipMaskData = 1;
1396 mWebgl->UniformData(LOCAL_GL_INT, mImageProgramClipMask, false,
1397 {(const uint8_t*)&clipMaskData, sizeof(clipMaskData)});
1399 return true;
1402 void DrawTargetWebgl::SharedContext::EnableScissor(const IntRect& aRect) {
1403 // Only update scissor state if it actually changes.
1404 if (!mLastScissor.IsEqualEdges(aRect)) {
1405 mLastScissor = aRect;
1406 mWebgl->Scissor(aRect.x, aRect.y, aRect.width, aRect.height);
1408 if (!mScissorEnabled) {
1409 mScissorEnabled = true;
1410 mWebgl->Enable(LOCAL_GL_SCISSOR_TEST);
1414 void DrawTargetWebgl::SharedContext::DisableScissor() {
1415 if (mScissorEnabled) {
1416 mScissorEnabled = false;
1417 mWebgl->Disable(LOCAL_GL_SCISSOR_TEST);
1421 inline ColorPattern DrawTargetWebgl::GetClearPattern() const {
1422 return ColorPattern(
1423 DeviceColor(0.0f, 0.0f, 0.0f, IsOpaque(mFormat) ? 1.0f : 0.0f));
1426 // Check if the transformed rect would contain the entire viewport.
1427 inline bool DrawTargetWebgl::RectContainsViewport(const Rect& aRect) const {
1428 return mTransform.PreservesAxisAlignedRectangles() &&
1429 MatrixDouble(mTransform)
1430 .TransformBounds(
1431 RectDouble(aRect.x, aRect.y, aRect.width, aRect.height))
1432 .Contains(RectDouble(GetRect()));
1435 // Ensure that the rect, after transform, is within reasonable precision limits
1436 // such that when transformed and clipped in the shader it will not round bits
1437 // from the mantissa in a way that will diverge in a noticeable way from path
1438 // geometry calculated by the path fallback.
1439 static inline bool RectInsidePrecisionLimits(const Rect& aRect,
1440 const Matrix& aTransform) {
1441 return Rect(-(1 << 20), -(1 << 20), 2 << 20, 2 << 20)
1442 .Contains(aTransform.TransformBounds(aRect));
1445 void DrawTargetWebgl::ClearRect(const Rect& aRect) {
1446 if (mIsClear) {
1447 // No need to clear anything if the entire framebuffer is already clear.
1448 return;
1451 bool containsViewport = RectContainsViewport(aRect);
1452 if (containsViewport) {
1453 // If the rect encompasses the entire viewport, just clear the viewport
1454 // instead to avoid transform issues.
1455 DrawRect(Rect(GetRect()), GetClearPattern(),
1456 DrawOptions(1.0f, CompositionOp::OP_CLEAR), Nothing(), nullptr,
1457 false);
1458 } else if (RectInsidePrecisionLimits(aRect, mTransform)) {
1459 // If the rect transform won't stress precision, then just use it.
1460 DrawRect(aRect, GetClearPattern(),
1461 DrawOptions(1.0f, CompositionOp::OP_CLEAR));
1462 } else {
1463 // Otherwise, using the transform in the shader may lead to inaccuracies, so
1464 // just fall back.
1465 MarkSkiaChanged();
1466 mSkia->ClearRect(aRect);
1469 // If the clear rectangle encompasses the entire viewport and is not clipped,
1470 // then mark the target as entirely clear.
1471 if (containsViewport && mSharedContext->IsCurrentTarget(this) &&
1472 !mSharedContext->HasClipMask() &&
1473 mSharedContext->mClipAARect.Contains(Rect(GetRect()))) {
1474 mIsClear = true;
1478 static inline DeviceColor PremultiplyColor(const DeviceColor& aColor,
1479 float aAlpha = 1.0f) {
1480 float a = aColor.a * aAlpha;
1481 return DeviceColor(aColor.r * a, aColor.g * a, aColor.b * a, a);
1484 // Attempts to create the framebuffer used for drawing and also any relevant
1485 // non-shared resources. Returns whether or not this succeeded.
1486 bool DrawTargetWebgl::CreateFramebuffer() {
1487 RefPtr<ClientWebGLContext> webgl = mSharedContext->mWebgl;
1488 if (!mFramebuffer) {
1489 mFramebuffer = webgl->CreateFramebuffer();
1491 if (!mTex) {
1492 mTex = webgl->CreateTexture();
1493 webgl->BindTexture(LOCAL_GL_TEXTURE_2D, mTex);
1494 webgl->TexStorage2D(LOCAL_GL_TEXTURE_2D, 1, LOCAL_GL_RGBA8, mSize.width,
1495 mSize.height);
1496 mSharedContext->InitTexParameters(mTex);
1497 webgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mFramebuffer);
1498 webgl->FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
1499 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D,
1500 mTex, 0);
1501 webgl->Viewport(0, 0, mSize.width, mSize.height);
1502 mSharedContext->DisableScissor();
1503 DeviceColor color = PremultiplyColor(GetClearPattern().mColor);
1504 webgl->ClearColor(color.b, color.g, color.r, color.a);
1505 webgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT);
1506 mSharedContext->ClearTarget();
1507 mSharedContext->ClearLastTexture();
1509 return true;
1512 void DrawTargetWebgl::CopySurface(SourceSurface* aSurface,
1513 const IntRect& aSourceRect,
1514 const IntPoint& aDestination) {
1515 // Intersect the source and destination rectangles with the viewport bounds.
1516 IntRect destRect =
1517 IntRect(aDestination, aSourceRect.Size()).SafeIntersect(GetRect());
1518 IntRect srcRect = destRect - aDestination + aSourceRect.TopLeft();
1519 if (srcRect.IsEmpty()) {
1520 return;
1523 if (mSkiaValid) {
1524 if (mSkiaLayer) {
1525 if (destRect.Contains(GetRect())) {
1526 // If the the destination would override the entire layer, discard the
1527 // layer.
1528 mSkiaLayer = false;
1529 } else if (!IsOpaque(aSurface->GetFormat())) {
1530 // If the surface is not opaque, copying it into the layer results in
1531 // unintended blending rather than a copy to the destination.
1532 FlattenSkia();
1534 } else {
1535 // If there is no layer, copying is safe.
1536 MarkSkiaChanged();
1538 mSkia->CopySurface(aSurface, srcRect, destRect.TopLeft());
1539 return;
1542 IntRect samplingRect;
1543 if (!mSharedContext->IsCompatibleSurface(aSurface)) {
1544 // If this data surface completely overwrites the framebuffer, then just
1545 // copy it to the Skia target.
1546 if (destRect.Contains(GetRect())) {
1547 MarkSkiaChanged(true);
1548 mSkia->DetachAllSnapshots();
1549 mSkiaNoClip->CopySurface(aSurface, srcRect, destRect.TopLeft());
1550 return;
1553 // CopySurface usually only samples a surface once, so don't cache the
1554 // entire surface as it is unlikely to be reused. Limit it to the used
1555 // source rectangle instead.
1556 IntRect surfaceRect = aSurface->GetRect();
1557 if (!srcRect.IsEqualEdges(surfaceRect)) {
1558 samplingRect = srcRect.SafeIntersect(surfaceRect);
1562 Matrix matrix = Matrix::Translation(destRect.TopLeft() - srcRect.TopLeft());
1563 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, matrix,
1564 SamplingFilter::POINT, samplingRect);
1565 DrawRect(Rect(destRect), pattern, DrawOptions(1.0f, CompositionOp::OP_SOURCE),
1566 Nothing(), nullptr, false, false);
1569 void DrawTargetWebgl::PushClip(const Path* aPath) {
1570 if (aPath && aPath->GetBackendType() == BackendType::SKIA) {
1571 // Detect if the path is really just a rect to simplify caching.
1572 const PathSkia* pathSkia = static_cast<const PathSkia*>(aPath);
1573 const SkPath& skPath = pathSkia->GetPath();
1574 SkRect rect = SkRect::MakeEmpty();
1575 if (skPath.isRect(&rect)) {
1576 PushClipRect(SkRectToRect(rect));
1577 return;
1581 mClipChanged = true;
1582 mRefreshClipState = true;
1583 mSkia->PushClip(aPath);
1585 mClipStack.push_back({GetTransform(), Rect(), aPath});
1588 void DrawTargetWebgl::PushClipRect(const Rect& aRect) {
1589 mClipChanged = true;
1590 mRefreshClipState = true;
1591 mSkia->PushClipRect(aRect);
1593 mClipStack.push_back({GetTransform(), aRect, nullptr});
1596 void DrawTargetWebgl::PushDeviceSpaceClipRects(const IntRect* aRects,
1597 uint32_t aCount) {
1598 mClipChanged = true;
1599 mRefreshClipState = true;
1600 mSkia->PushDeviceSpaceClipRects(aRects, aCount);
1602 for (uint32_t i = 0; i < aCount; i++) {
1603 mClipStack.push_back({Matrix(), Rect(aRects[i]), nullptr});
1607 void DrawTargetWebgl::PopClip() {
1608 mClipChanged = true;
1609 mRefreshClipState = true;
1610 mSkia->PopClip();
1612 mClipStack.pop_back();
1615 bool DrawTargetWebgl::RemoveAllClips() {
1616 if (mClipStack.empty()) {
1617 return true;
1619 if (!mSkia->RemoveAllClips()) {
1620 return false;
1622 mClipChanged = true;
1623 mRefreshClipState = true;
1624 mClipStack.clear();
1625 return true;
1628 // Whether a given composition operator can be mapped to a WebGL blend mode.
1629 static inline bool SupportsDrawOptions(const DrawOptions& aOptions) {
1630 switch (aOptions.mCompositionOp) {
1631 case CompositionOp::OP_OVER:
1632 case CompositionOp::OP_ADD:
1633 case CompositionOp::OP_ATOP:
1634 case CompositionOp::OP_SOURCE:
1635 case CompositionOp::OP_CLEAR:
1636 return true;
1637 default:
1638 return false;
1642 // Whether a pattern can be mapped to an available WebGL shader.
1643 bool DrawTargetWebgl::SharedContext::SupportsPattern(const Pattern& aPattern) {
1644 switch (aPattern.GetType()) {
1645 case PatternType::COLOR:
1646 return true;
1647 case PatternType::SURFACE: {
1648 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1649 if (surfacePattern.mExtendMode != ExtendMode::CLAMP) {
1650 return false;
1652 if (surfacePattern.mSurface) {
1653 // If the surface is already uploaded to a texture, then just use it.
1654 if (IsCompatibleSurface(surfacePattern.mSurface)) {
1655 return true;
1658 IntSize size = surfacePattern.mSurface->GetSize();
1659 // The maximum size a surface can be before triggering a fallback to
1660 // software. Bound the maximum surface size by the actual texture size
1661 // limit.
1662 int32_t maxSize = int32_t(
1663 std::min(StaticPrefs::gfx_canvas_accelerated_max_surface_size(),
1664 mMaxTextureSize));
1665 // Check if either of the surface dimensions or the sampling rect,
1666 // if supplied, exceed the maximum.
1667 if (std::max(size.width, size.height) > maxSize &&
1668 (surfacePattern.mSamplingRect.IsEmpty() ||
1669 std::max(surfacePattern.mSamplingRect.width,
1670 surfacePattern.mSamplingRect.height) > maxSize)) {
1671 return false;
1674 return true;
1676 default:
1677 // Patterns other than colors and surfaces are currently not accelerated.
1678 return false;
1682 // Whether a given composition operator is associative and thus allows drawing
1683 // into a separate layer that can be later composited back into the WebGL
1684 // context.
1685 static inline bool SupportsLayering(const DrawOptions& aOptions) {
1686 switch (aOptions.mCompositionOp) {
1687 case CompositionOp::OP_OVER:
1688 // Layering is only supported for the default source-over composition op.
1689 return true;
1690 default:
1691 return false;
1695 // When a texture handle is no longer referenced, it must mark itself unused
1696 // by unlinking its owning surface.
1697 static void ReleaseTextureHandle(void* aPtr) {
1698 static_cast<TextureHandle*>(aPtr)->SetSurface(nullptr);
1701 bool DrawTargetWebgl::DrawRect(const Rect& aRect, const Pattern& aPattern,
1702 const DrawOptions& aOptions,
1703 Maybe<DeviceColor> aMaskColor,
1704 RefPtr<TextureHandle>* aHandle,
1705 bool aTransformed, bool aClipped,
1706 bool aAccelOnly, bool aForceUpdate,
1707 const StrokeOptions* aStrokeOptions) {
1708 // If there is nothing to draw, then don't draw...
1709 if (aRect.IsEmpty()) {
1710 return true;
1713 // If we're already drawing directly to the WebGL context, then we want to
1714 // continue to do so. However, if we're drawing into a Skia layer over the
1715 // WebGL context, then we need to be careful to avoid repeatedly clearing
1716 // and flushing the layer if we hit a drawing request that can be accelerated
1717 // in between layered drawing requests, as clearing and flushing the layer
1718 // can be significantly expensive when repeated. So when a Skia layer is
1719 // active, if it is possible to continue drawing into the layer, then don't
1720 // accelerate the drawing request.
1721 if (mWebglValid || (mSkiaLayer && !mLayerDepth &&
1722 (aAccelOnly || !SupportsLayering(aOptions)))) {
1723 // If we get here, either the WebGL context is being directly drawn to
1724 // or we are going to flush the Skia layer to it before doing so. The shared
1725 // context still needs to be claimed and prepared for drawing. If this
1726 // fails, we just fall back to drawing with Skia below.
1727 if (PrepareContext(aClipped)) {
1728 // The shared context is claimed and the framebuffer is now valid, so try
1729 // accelerated drawing.
1730 return mSharedContext->DrawRectAccel(
1731 aRect, aPattern, aOptions, aMaskColor, aHandle, aTransformed,
1732 aClipped, aAccelOnly, aForceUpdate, aStrokeOptions);
1736 // Either there is no valid WebGL target to draw into, or we failed to prepare
1737 // it for drawing. The only thing we can do at this point is fall back to
1738 // drawing with Skia. If the request explicitly requires accelerated drawing,
1739 // then draw nothing before returning failure.
1740 if (!aAccelOnly) {
1741 DrawRectFallback(aRect, aPattern, aOptions, aMaskColor, aTransformed,
1742 aClipped, aStrokeOptions);
1744 return false;
1747 void DrawTargetWebgl::DrawRectFallback(const Rect& aRect,
1748 const Pattern& aPattern,
1749 const DrawOptions& aOptions,
1750 Maybe<DeviceColor> aMaskColor,
1751 bool aTransformed, bool aClipped,
1752 const StrokeOptions* aStrokeOptions) {
1753 // Invalidate the WebGL target and prepare the Skia target for drawing.
1754 MarkSkiaChanged(aOptions);
1756 if (aTransformed) {
1757 // If transforms are requested, then just translate back to FillRect.
1758 if (aMaskColor) {
1759 mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions);
1760 } else if (aStrokeOptions) {
1761 mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions);
1762 } else {
1763 mSkia->FillRect(aRect, aPattern, aOptions);
1765 } else if (aClipped) {
1766 // If no transform was requested but clipping is still required, then
1767 // temporarily reset the transform before translating to FillRect.
1768 mSkia->SetTransform(Matrix());
1769 if (aMaskColor) {
1770 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1771 if (surfacePattern.mSamplingRect.IsEmpty()) {
1772 mSkia->MaskSurface(ColorPattern(*aMaskColor), surfacePattern.mSurface,
1773 aRect.TopLeft(), aOptions);
1774 } else {
1775 mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions);
1777 } else if (aStrokeOptions) {
1778 mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions);
1779 } else {
1780 mSkia->FillRect(aRect, aPattern, aOptions);
1782 mSkia->SetTransform(mTransform);
1783 } else if (aPattern.GetType() == PatternType::SURFACE) {
1784 // No transform nor clipping was requested, so it is essentially just a
1785 // copy.
1786 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
1787 mSkia->CopySurface(surfacePattern.mSurface,
1788 surfacePattern.mSurface->GetRect(),
1789 IntPoint::Round(aRect.TopLeft()));
1790 } else {
1791 MOZ_ASSERT(false);
1795 inline already_AddRefed<WebGLTextureJS>
1796 DrawTargetWebgl::SharedContext::GetCompatibleSnapshot(
1797 SourceSurface* aSurface) const {
1798 if (aSurface->GetType() == SurfaceType::WEBGL) {
1799 RefPtr<SourceSurfaceWebgl> webglSurf =
1800 static_cast<SourceSurfaceWebgl*>(aSurface);
1801 if (this == webglSurf->mSharedContext) {
1802 // If there is a snapshot copy in a texture handle, use that.
1803 if (webglSurf->mHandle) {
1804 return do_AddRef(
1805 webglSurf->mHandle->GetBackingTexture()->GetWebGLTexture());
1807 if (RefPtr<DrawTargetWebgl> webglDT = webglSurf->GetTarget()) {
1808 // If there is a copy-on-write reference to a target, use its backing
1809 // texture directly. This is only safe if the targets don't match, but
1810 // MarkChanged should ensure that any snapshots were copied into a
1811 // texture handle before we ever get here.
1812 if (!IsCurrentTarget(webglDT)) {
1813 return do_AddRef(webglDT->mTex);
1818 return nullptr;
1821 inline bool DrawTargetWebgl::SharedContext::IsCompatibleSurface(
1822 SourceSurface* aSurface) const {
1823 return bool(RefPtr<WebGLTextureJS>(GetCompatibleSnapshot(aSurface)));
1826 bool DrawTargetWebgl::SharedContext::UploadSurface(
1827 DataSourceSurface* aData, SurfaceFormat aFormat, const IntRect& aSrcRect,
1828 const IntPoint& aDstOffset, bool aInit, bool aZero,
1829 const RefPtr<WebGLTextureJS>& aTex) {
1830 webgl::TexUnpackBlobDesc texDesc = {
1831 LOCAL_GL_TEXTURE_2D,
1832 {uint32_t(aSrcRect.width), uint32_t(aSrcRect.height), 1}};
1833 if (aData) {
1834 // The surface needs to be uploaded to its backing texture either to
1835 // initialize or update the texture handle contents. Map the data
1836 // contents of the surface so it can be read.
1837 DataSourceSurface::ScopedMap map(aData, DataSourceSurface::READ);
1838 if (!map.IsMapped()) {
1839 return false;
1841 int32_t stride = map.GetStride();
1842 int32_t bpp = BytesPerPixel(aFormat);
1843 if (mCurrentTarget && mCurrentTarget->mShmem.IsWritable() &&
1844 map.GetData() == mCurrentTarget->mShmem.get<uint8_t>()) {
1845 texDesc.sd = Some(layers::SurfaceDescriptorBuffer(
1846 layers::RGBDescriptor(mCurrentTarget->mSize, SurfaceFormat::R8G8B8A8),
1847 mCurrentTarget->mShmem));
1848 texDesc.structuredSrcSize =
1849 uvec2::From(stride / bpp, mCurrentTarget->mSize.height);
1850 texDesc.unpacking.skipPixels = aSrcRect.x;
1851 texDesc.unpacking.skipRows = aSrcRect.y;
1852 mWaitForShmem = true;
1853 } else {
1854 // Get the data pointer range considering the sampling rect offset and
1855 // size.
1856 Range<const uint8_t> range(
1857 map.GetData() + aSrcRect.y * size_t(stride) + aSrcRect.x * bpp,
1858 std::max(aSrcRect.height - 1, 0) * size_t(stride) +
1859 aSrcRect.width * bpp);
1860 texDesc.cpuData = Some(RawBuffer(range));
1862 // If the stride happens to be 4 byte aligned, assume that is the
1863 // desired alignment regardless of format (even A8). Otherwise, we
1864 // default to byte alignment.
1865 texDesc.unpacking.alignmentInTypeElems = stride % 4 ? 1 : 4;
1866 texDesc.unpacking.rowLength = stride / bpp;
1867 } else if (aZero) {
1868 // Create a PBO filled with zero data to initialize the texture data and
1869 // avoid slow initialization inside WebGL.
1870 MOZ_ASSERT(aSrcRect.TopLeft() == IntPoint(0, 0));
1871 size_t size =
1872 size_t(GetAlignedStride<4>(aSrcRect.width, BytesPerPixel(aFormat))) *
1873 aSrcRect.height;
1874 if (!mZeroBuffer || size > mZeroSize) {
1875 mZeroBuffer = mWebgl->CreateBuffer();
1876 mZeroSize = size;
1877 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mZeroBuffer);
1878 // WebGL will zero initialize the empty buffer, so we don't send zero data
1879 // explicitly.
1880 mWebgl->RawBufferData(LOCAL_GL_PIXEL_UNPACK_BUFFER, nullptr, size,
1881 LOCAL_GL_STATIC_DRAW);
1882 } else {
1883 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mZeroBuffer);
1885 texDesc.pboOffset = Some(0);
1887 // Upload as RGBA8 to avoid swizzling during upload. Surfaces provide
1888 // data as BGRA, but we manually swizzle that in the shader. An A8
1889 // surface will be stored as an R8 texture that will also be swizzled
1890 // in the shader.
1891 GLenum intFormat =
1892 aFormat == SurfaceFormat::A8 ? LOCAL_GL_R8 : LOCAL_GL_RGBA8;
1893 GLenum extFormat =
1894 aFormat == SurfaceFormat::A8 ? LOCAL_GL_RED : LOCAL_GL_RGBA;
1895 webgl::PackingInfo texPI = {extFormat, LOCAL_GL_UNSIGNED_BYTE};
1896 // Do the (partial) upload for the shared or standalone texture.
1897 if (aTex) {
1898 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, aTex);
1900 mWebgl->RawTexImage(0, aInit ? intFormat : 0,
1901 {uint32_t(aDstOffset.x), uint32_t(aDstOffset.y), 0},
1902 texPI, std::move(texDesc));
1903 if (aTex) {
1904 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mLastTexture);
1906 if (!aData && aZero) {
1907 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
1909 return true;
1912 // Allocate a new texture handle backed by either a standalone texture or as a
1913 // sub-texture of a larger shared texture.
1914 already_AddRefed<TextureHandle>
1915 DrawTargetWebgl::SharedContext::AllocateTextureHandle(SurfaceFormat aFormat,
1916 const IntSize& aSize,
1917 bool aAllowShared,
1918 bool aRenderable) {
1919 RefPtr<TextureHandle> handle;
1920 // Calculate the bytes that would be used by this texture handle, and prune
1921 // enough other textures to ensure we have that much usable texture space
1922 // available to allocate.
1923 size_t usedBytes = BackingTexture::UsedBytes(aFormat, aSize);
1924 PruneTextureMemory(usedBytes, false);
1925 // The requested page size for shared textures.
1926 int32_t pageSize = int32_t(std::min(
1927 StaticPrefs::gfx_canvas_accelerated_shared_page_size(), mMaxTextureSize));
1928 if (aAllowShared && std::max(aSize.width, aSize.height) <= pageSize / 2) {
1929 // Ensure that the surface is no bigger than a quadrant of a shared texture
1930 // page. If so, try to allocate it to a shared texture. Look for any
1931 // existing shared texture page with a matching format and allocate
1932 // from that if possible.
1933 for (auto& shared : mSharedTextures) {
1934 if (shared->GetFormat() == aFormat &&
1935 shared->IsRenderable() == aRenderable) {
1936 bool wasEmpty = !shared->HasAllocatedHandles();
1937 handle = shared->Allocate(aSize);
1938 if (handle) {
1939 if (wasEmpty) {
1940 // If the page was previously empty, then deduct it from the
1941 // empty memory reserves.
1942 mEmptyTextureMemory -= shared->UsedBytes();
1944 break;
1948 // If we couldn't find an existing shared texture page with matching
1949 // format, then allocate a new page to put the request in.
1950 if (!handle) {
1951 if (RefPtr<WebGLTextureJS> tex = mWebgl->CreateTexture()) {
1952 RefPtr<SharedTexture> shared =
1953 new SharedTexture(IntSize(pageSize, pageSize), aFormat, tex);
1954 if (aRenderable) {
1955 shared->MarkRenderable();
1957 mSharedTextures.push_back(shared);
1958 mTotalTextureMemory += shared->UsedBytes();
1959 handle = shared->Allocate(aSize);
1962 } else {
1963 // The surface wouldn't fit in a shared texture page, so we need to
1964 // allocate a standalone texture for it instead.
1965 if (RefPtr<WebGLTextureJS> tex = mWebgl->CreateTexture()) {
1966 RefPtr<StandaloneTexture> standalone =
1967 new StandaloneTexture(aSize, aFormat, tex);
1968 if (aRenderable) {
1969 standalone->MarkRenderable();
1971 mStandaloneTextures.push_back(standalone);
1972 mTotalTextureMemory += standalone->UsedBytes();
1973 handle = standalone;
1977 if (!handle) {
1978 return nullptr;
1981 // Insert the new texture handle into the front of the MRU list and
1982 // update used space for it.
1983 mTextureHandles.insertFront(handle);
1984 ++mNumTextureHandles;
1985 mUsedTextureMemory += handle->UsedBytes();
1987 return handle.forget();
1990 static inline SamplingFilter GetSamplingFilter(const Pattern& aPattern) {
1991 return aPattern.GetType() == PatternType::SURFACE
1992 ? static_cast<const SurfacePattern&>(aPattern).mSamplingFilter
1993 : SamplingFilter::GOOD;
1996 static inline bool UseNearestFilter(const Pattern& aPattern) {
1997 return GetSamplingFilter(aPattern) == SamplingFilter::POINT;
2000 // Determine if the rectangle is still axis-aligned and pixel-aligned.
2001 static inline Maybe<IntRect> IsAlignedRect(bool aTransformed,
2002 const Matrix& aCurrentTransform,
2003 const Rect& aRect) {
2004 if (!aTransformed || aCurrentTransform.HasOnlyIntegerTranslation()) {
2005 auto intRect = RoundedToInt(aRect);
2006 if (aRect.WithinEpsilonOf(Rect(intRect), 1.0e-3f)) {
2007 if (aTransformed) {
2008 intRect += RoundedToInt(aCurrentTransform.GetTranslation());
2010 return Some(intRect);
2013 return Nothing();
2016 template <class T, size_t N>
2017 void DrawTargetWebgl::SharedContext::MaybeUniformData(
2018 GLenum aFuncElemType, const WebGLUniformLocationJS* const aLoc,
2019 const Array<T, N>& aData, Maybe<Array<T, N>>& aCached) {
2020 if (aCached.isNothing() || !(*aCached == aData)) {
2021 aCached = Some(aData);
2022 Span<const uint8_t> bytes = AsBytes(Span(aData));
2023 // We currently always pass false for transpose. If in the future we need
2024 // support for transpose then caching needs to take that in to account.
2025 mWebgl->UniformData(aFuncElemType, aLoc, false, bytes);
2029 // Common rectangle and pattern drawing function shared by many DrawTarget
2030 // commands. If aMaskColor is specified, the provided surface pattern will be
2031 // treated as a mask. If aHandle is specified, then the surface pattern's
2032 // texture will be cached in the supplied handle, as opposed to using the
2033 // surface's user data. If aTransformed or aClipped are false, then transforms
2034 // and/or clipping will be disabled. If aAccelOnly is specified, then this
2035 // function will return before it would have otherwise drawn without
2036 // acceleration. If aForceUpdate is specified, then the provided texture handle
2037 // will be respecified with the provided surface.
2038 bool DrawTargetWebgl::SharedContext::DrawRectAccel(
2039 const Rect& aRect, const Pattern& aPattern, const DrawOptions& aOptions,
2040 Maybe<DeviceColor> aMaskColor, RefPtr<TextureHandle>* aHandle,
2041 bool aTransformed, bool aClipped, bool aAccelOnly, bool aForceUpdate,
2042 const StrokeOptions* aStrokeOptions, const PathVertexRange* aVertexRange) {
2043 // If the rect or clip rect is empty, then there is nothing to draw.
2044 if (aRect.IsEmpty() || mClipRect.IsEmpty()) {
2045 return true;
2048 // Check if the drawing options and the pattern support acceleration. Also
2049 // ensure the framebuffer is prepared for drawing. If not, fall back to using
2050 // the Skia target.
2051 if (!SupportsDrawOptions(aOptions) || !SupportsPattern(aPattern) ||
2052 aStrokeOptions || !mCurrentTarget->MarkChanged()) {
2053 // If only accelerated drawing was requested, bail out without software
2054 // drawing fallback.
2055 if (!aAccelOnly) {
2056 MOZ_ASSERT(!aVertexRange);
2057 mCurrentTarget->DrawRectFallback(aRect, aPattern, aOptions, aMaskColor,
2058 aTransformed, aClipped, aStrokeOptions);
2060 return false;
2063 const Matrix& currentTransform = GetTransform();
2065 if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE && aTransformed &&
2066 aClipped &&
2067 (HasClipMask() || !currentTransform.PreservesAxisAlignedRectangles() ||
2068 !currentTransform.TransformBounds(aRect).Contains(Rect(mClipAARect)) ||
2069 (aPattern.GetType() == PatternType::SURFACE &&
2070 !IsAlignedRect(aTransformed, currentTransform, aRect)))) {
2071 // Clear outside the mask region for masks that are not bounded by clip.
2072 return DrawRectAccel(Rect(mClipRect), ColorPattern(DeviceColor(0, 0, 0, 0)),
2073 DrawOptions(1.0f, CompositionOp::OP_SOURCE,
2074 aOptions.mAntialiasMode),
2075 Nothing(), nullptr, false, aClipped, aAccelOnly) &&
2076 DrawRectAccel(aRect, aPattern,
2077 DrawOptions(aOptions.mAlpha, CompositionOp::OP_ADD,
2078 aOptions.mAntialiasMode),
2079 aMaskColor, aHandle, aTransformed, aClipped,
2080 aAccelOnly, aForceUpdate, aStrokeOptions,
2081 aVertexRange);
2083 if (aOptions.mCompositionOp == CompositionOp::OP_CLEAR &&
2084 aPattern.GetType() == PatternType::SURFACE && !aMaskColor) {
2085 // If the surface being drawn with clear is not a mask, then its contents
2086 // needs to be ignored. Just use a color pattern instead.
2087 return DrawRectAccel(aRect, ColorPattern(DeviceColor(1, 1, 1, 1)), aOptions,
2088 Nothing(), aHandle, aTransformed, aClipped, aAccelOnly,
2089 aForceUpdate, aStrokeOptions, aVertexRange);
2092 // Set up the scissor test to reflect the clipping rectangle, if supplied.
2093 if (!mClipRect.Contains(IntRect(IntPoint(), mViewportSize))) {
2094 EnableScissor(mClipRect);
2095 } else {
2096 DisableScissor();
2099 bool success = false;
2101 // Now try to actually draw the pattern...
2102 switch (aPattern.GetType()) {
2103 case PatternType::COLOR: {
2104 if (!aVertexRange) {
2105 // Only an uncached draw if not using the vertex cache.
2106 mCurrentTarget->mProfile.OnUncachedDraw();
2108 DeviceColor color = PremultiplyColor(
2109 static_cast<const ColorPattern&>(aPattern).mColor, aOptions.mAlpha);
2110 if (((color.a == 1.0f &&
2111 aOptions.mCompositionOp == CompositionOp::OP_OVER) ||
2112 aOptions.mCompositionOp == CompositionOp::OP_SOURCE ||
2113 aOptions.mCompositionOp == CompositionOp::OP_CLEAR) &&
2114 !aStrokeOptions && !aVertexRange && !HasClipMask() &&
2115 mClipAARect.IsEqualEdges(Rect(mClipRect))) {
2116 // Certain color patterns can be mapped to scissored clears. The
2117 // composition op must effectively overwrite the destination, and the
2118 // transform must map to an axis-aligned integer rectangle.
2119 if (Maybe<IntRect> intRect =
2120 IsAlignedRect(aTransformed, currentTransform, aRect)) {
2121 // Only use a clear if the area is larger than a quarter or the
2122 // viewport.
2123 if (intRect->Area() >=
2124 (mViewportSize.width / 2) * (mViewportSize.height / 2)) {
2125 if (!intRect->Contains(mClipRect)) {
2126 EnableScissor(intRect->Intersect(mClipRect));
2128 if (aOptions.mCompositionOp == CompositionOp::OP_CLEAR) {
2129 color =
2130 PremultiplyColor(mCurrentTarget->GetClearPattern().mColor);
2132 mWebgl->ClearColor(color.b, color.g, color.r, color.a);
2133 mWebgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT);
2134 success = true;
2135 break;
2139 // Map the composition op to a WebGL blend mode, if possible.
2140 Maybe<DeviceColor> blendColor;
2141 if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE ||
2142 aOptions.mCompositionOp == CompositionOp::OP_CLEAR) {
2143 // The source operator can support clipping and AA by emulating it with
2144 // the over op. Supply the color with blend state, and set the shader
2145 // color to white, to avoid needing dual-source blending.
2146 blendColor = Some(color);
2147 // Both source and clear operators should output a mask from the shader.
2148 color = DeviceColor(1, 1, 1, 1);
2150 SetBlendState(aOptions.mCompositionOp, blendColor);
2151 // Since it couldn't be mapped to a scissored clear, we need to use the
2152 // solid color shader with supplied transform.
2153 if (mLastProgram != mSolidProgram) {
2154 mWebgl->UseProgram(mSolidProgram);
2155 mLastProgram = mSolidProgram;
2157 Array<float, 2> viewportData = {float(mViewportSize.width),
2158 float(mViewportSize.height)};
2159 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramViewport, viewportData,
2160 mSolidProgramUniformState.mViewport);
2162 // Generated paths provide their own AA as vertex alpha.
2163 Array<float, 1> aaData = {aVertexRange ? 0.0f : 1.0f};
2164 MaybeUniformData(LOCAL_GL_FLOAT, mSolidProgramAA, aaData,
2165 mSolidProgramUniformState.mAA);
2167 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel
2168 // boundary.
2169 Array<float, 4> clipData = {mClipAARect.x - 0.5f, mClipAARect.y - 0.5f,
2170 mClipAARect.XMost() + 0.5f,
2171 mClipAARect.YMost() + 0.5f};
2172 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramClipBounds, clipData,
2173 mSolidProgramUniformState.mClipBounds);
2175 Array<float, 4> colorData = {color.b, color.g, color.r, color.a};
2176 Matrix xform(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y);
2177 if (aTransformed) {
2178 xform *= currentTransform;
2180 Array<float, 6> xformData = {xform._11, xform._12, xform._21,
2181 xform._22, xform._31, xform._32};
2182 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramTransform, xformData,
2183 mSolidProgramUniformState.mTransform);
2185 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramColor, colorData,
2186 mSolidProgramUniformState.mColor);
2188 // Finally draw the colored rectangle.
2189 if (aVertexRange) {
2190 // If there's a vertex range, then we need to draw triangles within from
2191 // generated from a path stored in the path vertex buffer.
2192 mWebgl->DrawArrays(LOCAL_GL_TRIANGLES, GLint(aVertexRange->mOffset),
2193 GLsizei(aVertexRange->mLength));
2194 } else {
2195 // Otherwise we're drawing a simple filled rectangle.
2196 mWebgl->DrawArrays(LOCAL_GL_TRIANGLE_FAN, 0, 4);
2198 success = true;
2199 break;
2201 case PatternType::SURFACE: {
2202 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
2203 // If a texture handle was supplied, or if the surface already has an
2204 // assigned texture handle stashed in its used data, try to use it.
2205 RefPtr<TextureHandle> handle =
2206 aHandle ? aHandle->get()
2207 : (surfacePattern.mSurface
2208 ? static_cast<TextureHandle*>(
2209 surfacePattern.mSurface->GetUserData(
2210 &mTextureHandleKey))
2211 : nullptr);
2212 IntSize texSize;
2213 IntPoint offset;
2214 SurfaceFormat format;
2215 // Check if the found handle is still valid and if its sampling rect
2216 // matches the requested sampling rect.
2217 if (handle && handle->IsValid() &&
2218 (surfacePattern.mSamplingRect.IsEmpty() ||
2219 handle->GetSamplingRect().IsEqualEdges(
2220 surfacePattern.mSamplingRect))) {
2221 texSize = handle->GetSize();
2222 format = handle->GetFormat();
2223 offset = handle->GetSamplingOffset();
2224 } else {
2225 // Otherwise, there is no handle that can be used yet, so extract
2226 // information from the surface pattern.
2227 handle = nullptr;
2228 if (!surfacePattern.mSurface) {
2229 // If there was no actual surface supplied, then we tried to draw
2230 // using a texture handle, but the texture handle wasn't valid.
2231 break;
2233 texSize = surfacePattern.mSurface->GetSize();
2234 format = surfacePattern.mSurface->GetFormat();
2235 if (!surfacePattern.mSamplingRect.IsEmpty()) {
2236 texSize = surfacePattern.mSamplingRect.Size();
2237 offset = surfacePattern.mSamplingRect.TopLeft();
2241 // We need to be able to transform from local space into texture space.
2242 Matrix invMatrix = surfacePattern.mMatrix;
2243 // If drawing a pre-transformed vertex range, then we need to ensure the
2244 // user-space pattern is still transformed to screen-space.
2245 if (aVertexRange && !aTransformed) {
2246 invMatrix *= currentTransform;
2248 if (!invMatrix.Invert()) {
2249 break;
2252 RefPtr<WebGLTextureJS> tex;
2253 IntRect bounds;
2254 IntSize backingSize;
2255 RefPtr<DataSourceSurface> data;
2256 if (handle) {
2257 if (aForceUpdate) {
2258 data = surfacePattern.mSurface->GetDataSurface();
2259 if (!data) {
2260 break;
2262 // The size of the texture may change if we update contents.
2263 mUsedTextureMemory -= handle->UsedBytes();
2264 handle->UpdateSize(texSize);
2265 mUsedTextureMemory += handle->UsedBytes();
2266 handle->SetSamplingOffset(surfacePattern.mSamplingRect.TopLeft());
2268 // If using an existing handle, move it to the front of the MRU list.
2269 handle->remove();
2270 mTextureHandles.insertFront(handle);
2271 } else if ((tex = GetCompatibleSnapshot(surfacePattern.mSurface))) {
2272 backingSize = surfacePattern.mSurface->GetSize();
2273 bounds = IntRect(offset, texSize);
2274 // Count reusing a snapshot texture (no readback) as a cache hit.
2275 mCurrentTarget->mProfile.OnCacheHit();
2276 } else {
2277 // If we get here, we need a data surface for a texture upload.
2278 data = surfacePattern.mSurface->GetDataSurface();
2279 if (!data) {
2280 break;
2282 // There is no existing handle. Try to allocate a new one. If the
2283 // surface size may change via a forced update, then don't allocate
2284 // from a shared texture page.
2285 handle = AllocateTextureHandle(format, texSize, !aForceUpdate);
2286 if (!handle) {
2287 MOZ_ASSERT(false);
2288 break;
2290 // Link the handle to the surface's user data.
2291 handle->SetSamplingOffset(surfacePattern.mSamplingRect.TopLeft());
2292 if (aHandle) {
2293 *aHandle = handle;
2294 } else {
2295 handle->SetSurface(surfacePattern.mSurface);
2296 surfacePattern.mSurface->AddUserData(&mTextureHandleKey, handle.get(),
2297 ReleaseTextureHandle);
2301 // Map the composition op to a WebGL blend mode, if possible. If there is
2302 // a mask color and a texture with multiple channels, assume subpixel
2303 // blending. If we encounter the source op here, then assume the surface
2304 // is opaque (non-opaque is handled above) and emulate it with over.
2305 SetBlendState(aOptions.mCompositionOp,
2306 format != SurfaceFormat::A8 ? aMaskColor : Nothing());
2307 // Switch to the image shader and set up relevant transforms.
2308 if (mLastProgram != mImageProgram) {
2309 mWebgl->UseProgram(mImageProgram);
2310 mLastProgram = mImageProgram;
2313 Array<float, 2> viewportData = {float(mViewportSize.width),
2314 float(mViewportSize.height)};
2315 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mImageProgramViewport, viewportData,
2316 mImageProgramUniformState.mViewport);
2318 // AA is not supported for OP_SOURCE. Generated paths provide their own
2319 // AA as vertex alpha.
2320 Array<float, 1> aaData = {
2321 mLastCompositionOp == CompositionOp::OP_SOURCE || aVertexRange
2322 ? 0.0f
2323 : 1.0f};
2324 MaybeUniformData(LOCAL_GL_FLOAT, mImageProgramAA, aaData,
2325 mImageProgramUniformState.mAA);
2327 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel
2328 // boundary.
2329 Array<float, 4> clipData = {mClipAARect.x - 0.5f, mClipAARect.y - 0.5f,
2330 mClipAARect.XMost() + 0.5f,
2331 mClipAARect.YMost() + 0.5f};
2332 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mImageProgramClipBounds, clipData,
2333 mImageProgramUniformState.mClipBounds);
2335 DeviceColor color =
2336 mLastCompositionOp == CompositionOp::OP_CLEAR
2337 ? DeviceColor(1, 1, 1, 1)
2338 : PremultiplyColor(
2339 aMaskColor && format != SurfaceFormat::A8
2340 ? DeviceColor::Mask(1.0f, aMaskColor->a)
2341 : aMaskColor.valueOr(DeviceColor(1, 1, 1, 1)),
2342 aOptions.mAlpha);
2343 Array<float, 4> colorData = {color.b, color.g, color.r, color.a};
2344 Array<float, 1> swizzleData = {format == SurfaceFormat::A8 ? 1.0f : 0.0f};
2345 Matrix xform(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y);
2346 if (aTransformed) {
2347 xform *= currentTransform;
2349 Array<float, 6> xformData = {xform._11, xform._12, xform._21,
2350 xform._22, xform._31, xform._32};
2351 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mImageProgramTransform, xformData,
2352 mImageProgramUniformState.mTransform);
2354 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mImageProgramColor, colorData,
2355 mImageProgramUniformState.mColor);
2357 MaybeUniformData(LOCAL_GL_FLOAT, mImageProgramSwizzle, swizzleData,
2358 mImageProgramUniformState.mSwizzle);
2360 // Start binding the WebGL state for the texture.
2361 BackingTexture* backing = nullptr;
2362 if (handle) {
2363 backing = handle->GetBackingTexture();
2364 if (!tex) {
2365 tex = backing->GetWebGLTexture();
2367 bounds = handle->GetBounds();
2368 backingSize = backing->GetSize();
2370 if (mLastTexture != tex) {
2371 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex);
2372 mLastTexture = tex;
2375 if (backing && !backing->IsInitialized()) {
2376 // If this is the first time the texture is used, we need to initialize
2377 // the clamping and filtering state.
2378 backing->MarkInitialized();
2379 InitTexParameters(tex);
2380 if (texSize != backingSize) {
2381 // If this is a shared texture handle whose actual backing texture is
2382 // larger than it, then we need to allocate the texture page to the
2383 // full backing size before we can do a partial upload of the surface.
2384 UploadSurface(nullptr, format, IntRect(IntPoint(), backingSize),
2385 IntPoint(), true, true);
2389 if (data) {
2390 UploadSurface(data, format, IntRect(offset, texSize), bounds.TopLeft(),
2391 texSize == backingSize);
2392 // Signal that we had to upload new data to the texture cache.
2393 mCurrentTarget->mProfile.OnCacheMiss();
2394 } else {
2395 // Signal that we are reusing data from the texture cache.
2396 mCurrentTarget->mProfile.OnCacheHit();
2399 // Set up the texture coordinate matrix to map from the input rectangle to
2400 // the backing texture subrect.
2401 Size backingSizeF(backingSize);
2402 Matrix uvMatrix(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y);
2403 uvMatrix *= invMatrix;
2404 uvMatrix *= Matrix(1.0f / backingSizeF.width, 0.0f, 0.0f,
2405 1.0f / backingSizeF.height,
2406 float(bounds.x - offset.x) / backingSizeF.width,
2407 float(bounds.y - offset.y) / backingSizeF.height);
2408 Array<float, 6> uvData = {uvMatrix._11, uvMatrix._12, uvMatrix._21,
2409 uvMatrix._22, uvMatrix._31, uvMatrix._32};
2410 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mImageProgramTexMatrix, uvData,
2411 mImageProgramUniformState.mTexMatrix);
2413 // Clamp sampling to within the bounds of the backing texture subrect.
2414 Array<float, 4> texBounds = {
2415 (bounds.x + 0.5f) / backingSizeF.width,
2416 (bounds.y + 0.5f) / backingSizeF.height,
2417 (bounds.XMost() - 0.5f) / backingSizeF.width,
2418 (bounds.YMost() - 0.5f) / backingSizeF.height,
2420 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mImageProgramTexBounds, texBounds,
2421 mImageProgramUniformState.mTexBounds);
2423 // Ensure we use nearest filtering when no antialiasing is requested.
2424 if (UseNearestFilter(surfacePattern)) {
2425 SetTexFilter(tex, false);
2428 // Finally draw the image rectangle.
2429 if (aVertexRange) {
2430 // If there's a vertex range, then we need to draw triangles within from
2431 // generated from a path stored in the path vertex buffer.
2432 mWebgl->DrawArrays(LOCAL_GL_TRIANGLES, GLint(aVertexRange->mOffset),
2433 GLsizei(aVertexRange->mLength));
2434 } else {
2435 // Otherwise we're drawing a simple filled rectangle.
2436 mWebgl->DrawArrays(LOCAL_GL_TRIANGLE_FAN, 0, 4);
2439 // Restore the default linear filter if overridden.
2440 if (UseNearestFilter(surfacePattern)) {
2441 SetTexFilter(tex, true);
2444 success = true;
2445 break;
2447 default:
2448 gfxWarning() << "Unknown DrawTargetWebgl::DrawRect pattern type: "
2449 << (int)aPattern.GetType();
2450 break;
2452 // mWebgl->Disable(LOCAL_GL_BLEND);
2454 return success;
2457 bool DrawTargetWebgl::SharedContext::RemoveSharedTexture(
2458 const RefPtr<SharedTexture>& aTexture) {
2459 auto pos =
2460 std::find(mSharedTextures.begin(), mSharedTextures.end(), aTexture);
2461 if (pos == mSharedTextures.end()) {
2462 return false;
2464 // Keep around a reserve of empty pages to avoid initialization costs from
2465 // allocating shared pages. If still below the limit of reserved pages, then
2466 // just add it to the reserve. Otherwise, erase the empty texture page.
2467 size_t maxBytes = StaticPrefs::gfx_canvas_accelerated_reserve_empty_cache()
2468 << 20;
2469 size_t usedBytes = aTexture->UsedBytes();
2470 if (mEmptyTextureMemory + usedBytes <= maxBytes) {
2471 mEmptyTextureMemory += usedBytes;
2472 } else {
2473 mTotalTextureMemory -= usedBytes;
2474 mSharedTextures.erase(pos);
2475 ClearLastTexture();
2476 mWebgl->DeleteTexture(aTexture->GetWebGLTexture());
2478 return true;
2481 void SharedTextureHandle::Cleanup(DrawTargetWebgl::SharedContext& aContext) {
2482 mTexture->Free(*this);
2484 // Check if the shared handle's owning page has no more allocated handles
2485 // after we freed it. If so, remove the empty shared texture page also.
2486 if (!mTexture->HasAllocatedHandles()) {
2487 aContext.RemoveSharedTexture(mTexture);
2491 bool DrawTargetWebgl::SharedContext::RemoveStandaloneTexture(
2492 const RefPtr<StandaloneTexture>& aTexture) {
2493 auto pos = std::find(mStandaloneTextures.begin(), mStandaloneTextures.end(),
2494 aTexture);
2495 if (pos == mStandaloneTextures.end()) {
2496 return false;
2498 mTotalTextureMemory -= aTexture->UsedBytes();
2499 mStandaloneTextures.erase(pos);
2500 ClearLastTexture();
2501 mWebgl->DeleteTexture(aTexture->GetWebGLTexture());
2502 return true;
2505 void StandaloneTexture::Cleanup(DrawTargetWebgl::SharedContext& aContext) {
2506 aContext.RemoveStandaloneTexture(this);
2509 // Prune a given texture handle and release its associated resources.
2510 void DrawTargetWebgl::SharedContext::PruneTextureHandle(
2511 const RefPtr<TextureHandle>& aHandle) {
2512 // Invalidate the handle so nothing will subsequently use its contents.
2513 aHandle->Invalidate();
2514 // If the handle has an associated SourceSurface, unlink it.
2515 UnlinkSurfaceTexture(aHandle);
2516 // If the handle has an associated CacheEntry, unlink it.
2517 if (RefPtr<CacheEntry> entry = aHandle->GetCacheEntry()) {
2518 entry->Unlink();
2520 // Deduct the used space from the total.
2521 mUsedTextureMemory -= aHandle->UsedBytes();
2522 // Ensure any allocated shared or standalone texture regions get freed.
2523 aHandle->Cleanup(*this);
2526 // Prune any texture memory above the limit (or margin below the limit) or any
2527 // least-recently-used handles that are no longer associated with any usable
2528 // surface.
2529 bool DrawTargetWebgl::SharedContext::PruneTextureMemory(size_t aMargin,
2530 bool aPruneUnused) {
2531 // The maximum amount of texture memory that may be used by textures.
2532 size_t maxBytes = StaticPrefs::gfx_canvas_accelerated_cache_size() << 20;
2533 maxBytes -= std::min(maxBytes, aMargin);
2534 size_t maxItems = StaticPrefs::gfx_canvas_accelerated_cache_items();
2535 size_t oldItems = mNumTextureHandles;
2536 while (!mTextureHandles.isEmpty() &&
2537 (mUsedTextureMemory > maxBytes || mNumTextureHandles > maxItems ||
2538 (aPruneUnused && !mTextureHandles.getLast()->IsUsed()))) {
2539 PruneTextureHandle(mTextureHandles.popLast());
2540 --mNumTextureHandles;
2542 return mNumTextureHandles < oldItems;
2545 void DrawTargetWebgl::FillRect(const Rect& aRect, const Pattern& aPattern,
2546 const DrawOptions& aOptions) {
2547 if (SupportsPattern(aPattern)) {
2548 if (RectInsidePrecisionLimits(aRect, mTransform)) {
2549 DrawRect(aRect, aPattern, aOptions);
2550 return;
2552 if (aPattern.GetType() == PatternType::COLOR &&
2553 RectContainsViewport(aRect)) {
2554 // If the pattern is transform-invariant and the rect encompasses the
2555 // entire viewport, just clip drawing to the viewport to avoid transform
2556 // issues.
2557 DrawRect(Rect(GetRect()), aPattern, aOptions, Nothing(), nullptr, false);
2558 return;
2561 if (!mWebglValid) {
2562 MarkSkiaChanged(aOptions);
2563 mSkia->FillRect(aRect, aPattern, aOptions);
2564 } else {
2565 // If the pattern is unsupported, then transform the rect to a path so it
2566 // can be cached.
2567 SkPath skiaPath;
2568 skiaPath.addRect(RectToSkRect(aRect));
2569 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
2570 DrawPath(path, aPattern, aOptions);
2574 void CacheEntry::Link(const RefPtr<TextureHandle>& aHandle) {
2575 mHandle = aHandle;
2576 mHandle->SetCacheEntry(this);
2579 // When the CacheEntry becomes unused, it marks the corresponding
2580 // TextureHandle as unused and unlinks it from the CacheEntry. The
2581 // entry is removed from its containing Cache, if applicable.
2582 void CacheEntry::Unlink() {
2583 // The entry may not have a valid handle if rasterization failed.
2584 if (mHandle) {
2585 mHandle->SetCacheEntry(nullptr);
2586 mHandle = nullptr;
2589 RemoveFromList();
2592 // Hashes a path and pattern to a single hash value that can be used for quick
2593 // comparisons. This currently avoids to expensive hashing of internal path
2594 // and pattern data for speed, relying instead on later exact comparisons for
2595 // disambiguation.
2596 HashNumber PathCacheEntry::HashPath(const QuantizedPath& aPath,
2597 const Pattern* aPattern,
2598 const Matrix& aTransform,
2599 const IntRect& aBounds,
2600 const Point& aOrigin) {
2601 HashNumber hash = 0;
2602 hash = AddToHash(hash, aPath.mPath.num_types);
2603 hash = AddToHash(hash, aPath.mPath.num_points);
2604 if (aPath.mPath.num_points > 0) {
2605 hash = AddToHash(hash, aPath.mPath.points[0].x);
2606 hash = AddToHash(hash, aPath.mPath.points[0].y);
2607 if (aPath.mPath.num_points > 1) {
2608 hash = AddToHash(hash, aPath.mPath.points[1].x);
2609 hash = AddToHash(hash, aPath.mPath.points[1].y);
2612 // Quantize the relative offset of the path to its bounds.
2613 IntPoint offset = RoundedToInt((aOrigin - Point(aBounds.TopLeft())) * 16.0f);
2614 hash = AddToHash(hash, offset.x);
2615 hash = AddToHash(hash, offset.y);
2616 hash = AddToHash(hash, aBounds.width);
2617 hash = AddToHash(hash, aBounds.height);
2618 if (aPattern) {
2619 hash = AddToHash(hash, (int)aPattern->GetType());
2621 return hash;
2624 // When caching rendered geometry, we need to ensure the scale and orientation
2625 // is approximately the same. The offset will be considered separately.
2626 static inline bool HasMatchingScale(const Matrix& aTransform1,
2627 const Matrix& aTransform2) {
2628 return FuzzyEqual(aTransform1._11, aTransform2._11) &&
2629 FuzzyEqual(aTransform1._12, aTransform2._12) &&
2630 FuzzyEqual(aTransform1._21, aTransform2._21) &&
2631 FuzzyEqual(aTransform1._22, aTransform2._22);
2634 // Determines if an existing path cache entry matches an incoming path and
2635 // pattern.
2636 inline bool PathCacheEntry::MatchesPath(const QuantizedPath& aPath,
2637 const Pattern* aPattern,
2638 const StrokeOptions* aStrokeOptions,
2639 const Matrix& aTransform,
2640 const IntRect& aBounds,
2641 const Point& aOrigin, HashNumber aHash,
2642 float aSigma) {
2643 return aHash == mHash && HasMatchingScale(aTransform, mTransform) &&
2644 // Ensure the clipped relative bounds fit inside those of the entry
2645 aBounds.x - aOrigin.x >= mBounds.x - mOrigin.x &&
2646 (aBounds.x - aOrigin.x) + aBounds.width <=
2647 (mBounds.x - mOrigin.x) + mBounds.width &&
2648 aBounds.y - aOrigin.y >= mBounds.y - mOrigin.y &&
2649 (aBounds.y - aOrigin.y) + aBounds.height <=
2650 (mBounds.y - mOrigin.y) + mBounds.height &&
2651 aPath == mPath &&
2652 (!aPattern ? !mPattern : mPattern && *aPattern == *mPattern) &&
2653 (!aStrokeOptions
2654 ? !mStrokeOptions
2655 : mStrokeOptions && *aStrokeOptions == *mStrokeOptions) &&
2656 aSigma == mSigma;
2659 PathCacheEntry::PathCacheEntry(QuantizedPath&& aPath, Pattern* aPattern,
2660 StoredStrokeOptions* aStrokeOptions,
2661 const Matrix& aTransform, const IntRect& aBounds,
2662 const Point& aOrigin, HashNumber aHash,
2663 float aSigma)
2664 : CacheEntryImpl<PathCacheEntry>(aTransform, aBounds, aHash),
2665 mPath(std::move(aPath)),
2666 mOrigin(aOrigin),
2667 mPattern(aPattern),
2668 mStrokeOptions(aStrokeOptions),
2669 mSigma(aSigma) {}
2671 // Attempt to find a matching entry in the path cache. If one isn't found,
2672 // a new entry will be created. The caller should check whether the contained
2673 // texture handle is valid to determine if it will need to render the text run
2674 // or just reuse the cached texture.
2675 already_AddRefed<PathCacheEntry> PathCache::FindOrInsertEntry(
2676 QuantizedPath aPath, const Pattern* aPattern,
2677 const StrokeOptions* aStrokeOptions, const Matrix& aTransform,
2678 const IntRect& aBounds, const Point& aOrigin, float aSigma) {
2679 HashNumber hash =
2680 PathCacheEntry::HashPath(aPath, aPattern, aTransform, aBounds, aOrigin);
2681 for (const RefPtr<PathCacheEntry>& entry : GetChain(hash)) {
2682 if (entry->MatchesPath(aPath, aPattern, aStrokeOptions, aTransform, aBounds,
2683 aOrigin, hash, aSigma)) {
2684 return do_AddRef(entry);
2687 Pattern* pattern = nullptr;
2688 if (aPattern) {
2689 pattern = aPattern->CloneWeak();
2690 if (!pattern) {
2691 return nullptr;
2694 StoredStrokeOptions* strokeOptions = nullptr;
2695 if (aStrokeOptions) {
2696 strokeOptions = aStrokeOptions->Clone();
2697 if (!strokeOptions) {
2698 return nullptr;
2701 RefPtr<PathCacheEntry> entry =
2702 new PathCacheEntry(std::move(aPath), pattern, strokeOptions, aTransform,
2703 aBounds, aOrigin, hash, aSigma);
2704 Insert(entry);
2705 return entry.forget();
2708 void DrawTargetWebgl::Fill(const Path* aPath, const Pattern& aPattern,
2709 const DrawOptions& aOptions) {
2710 if (!aPath || aPath->GetBackendType() != BackendType::SKIA) {
2711 return;
2714 const SkPath& skiaPath = static_cast<const PathSkia*>(aPath)->GetPath();
2715 SkRect skiaRect = SkRect::MakeEmpty();
2716 // Draw the path as a simple rectangle with a supported pattern when possible.
2717 if (skiaPath.isRect(&skiaRect) && SupportsPattern(aPattern)) {
2718 Rect rect = SkRectToRect(skiaRect);
2719 if (RectInsidePrecisionLimits(rect, mTransform)) {
2720 DrawRect(rect, aPattern, aOptions);
2721 return;
2723 if (aPattern.GetType() == PatternType::COLOR &&
2724 RectContainsViewport(rect)) {
2725 // If the pattern is transform-invariant and the rect encompasses the
2726 // entire viewport, just clip drawing to the viewport to avoid transform
2727 // issues.
2728 DrawRect(Rect(GetRect()), aPattern, aOptions, Nothing(), nullptr, false);
2729 return;
2733 DrawPath(aPath, aPattern, aOptions);
2736 QuantizedPath::QuantizedPath(const WGR::Path& aPath) : mPath(aPath) {}
2738 QuantizedPath::QuantizedPath(QuantizedPath&& aPath) noexcept
2739 : mPath(aPath.mPath) {
2740 aPath.mPath.points = nullptr;
2741 aPath.mPath.num_points = 0;
2742 aPath.mPath.types = nullptr;
2743 aPath.mPath.num_types = 0;
2746 QuantizedPath::~QuantizedPath() {
2747 if (mPath.points || mPath.types) {
2748 WGR::wgr_path_release(mPath);
2752 bool QuantizedPath::operator==(const QuantizedPath& aOther) const {
2753 return mPath.num_types == aOther.mPath.num_types &&
2754 mPath.num_points == aOther.mPath.num_points &&
2755 mPath.fill_mode == aOther.mPath.fill_mode &&
2756 !memcmp(mPath.types, aOther.mPath.types,
2757 mPath.num_types * sizeof(uint8_t)) &&
2758 !memcmp(mPath.points, aOther.mPath.points,
2759 mPath.num_points * sizeof(WGR::Point));
2762 // Generate a quantized path from the Skia path using WGR. The supplied
2763 // transform will be applied to the path. The path is stored relative to its
2764 // bounds origin to support translation later.
2765 static Maybe<QuantizedPath> GenerateQuantizedPath(const SkPath& aPath,
2766 const Rect& aBounds,
2767 const Matrix& aTransform) {
2768 WGR::PathBuilder* pb = WGR::wgr_new_builder();
2769 if (!pb) {
2770 return Nothing();
2772 WGR::wgr_builder_set_fill_mode(pb,
2773 aPath.getFillType() == SkPathFillType::kWinding
2774 ? WGR::FillMode::Winding
2775 : WGR::FillMode::EvenOdd);
2777 SkPath::RawIter iter(aPath);
2778 SkPoint params[4];
2779 SkPath::Verb currentVerb;
2781 // printf_stderr("bounds: (%d, %d) %d x %d\n", aBounds.x, aBounds.y,
2782 // aBounds.width, aBounds.height);
2783 Matrix transform = aTransform;
2784 transform.PostTranslate(-aBounds.TopLeft());
2785 while ((currentVerb = iter.next(params)) != SkPath::kDone_Verb) {
2786 switch (currentVerb) {
2787 case SkPath::kMove_Verb: {
2788 Point p0 = transform.TransformPoint(SkPointToPoint(params[0]));
2789 WGR::wgr_builder_move_to(pb, p0.x, p0.y);
2790 break;
2792 case SkPath::kLine_Verb: {
2793 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2794 WGR::wgr_builder_line_to(pb, p1.x, p1.y);
2795 break;
2797 case SkPath::kCubic_Verb: {
2798 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2799 Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
2800 Point p3 = transform.TransformPoint(SkPointToPoint(params[3]));
2801 // printf_stderr("cubic (%f, %f), (%f, %f), (%f, %f)\n", p1.x, p1.y,
2802 // p2.x, p2.y, p3.x, p3.y);
2803 WGR::wgr_builder_curve_to(pb, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
2804 break;
2806 case SkPath::kQuad_Verb: {
2807 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2808 Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
2809 // printf_stderr("quad (%f, %f), (%f, %f)\n", p1.x, p1.y, p2.x, p2.y);
2810 WGR::wgr_builder_quad_to(pb, p1.x, p1.y, p2.x, p2.y);
2811 break;
2813 case SkPath::kConic_Verb: {
2814 Point p0 = transform.TransformPoint(SkPointToPoint(params[0]));
2815 Point p1 = transform.TransformPoint(SkPointToPoint(params[1]));
2816 Point p2 = transform.TransformPoint(SkPointToPoint(params[2]));
2817 float w = iter.conicWeight();
2818 std::vector<Point> quads;
2819 int numQuads = ConvertConicToQuads(p0, p1, p2, w, quads);
2820 for (int i = 0; i < numQuads; i++) {
2821 Point q1 = quads[2 * i + 1];
2822 Point q2 = quads[2 * i + 2];
2823 // printf_stderr("conic quad (%f, %f), (%f, %f)\n", q1.x, q1.y, q2.x,
2824 // q2.y);
2825 WGR::wgr_builder_quad_to(pb, q1.x, q1.y, q2.x, q2.y);
2827 break;
2829 case SkPath::kClose_Verb:
2830 // printf_stderr("close\n");
2831 WGR::wgr_builder_close(pb);
2832 break;
2833 default:
2834 MOZ_ASSERT(false);
2835 // Unexpected verb found in path!
2836 WGR::wgr_builder_release(pb);
2837 return Nothing();
2841 WGR::Path p = WGR::wgr_builder_get_path(pb);
2842 WGR::wgr_builder_release(pb);
2843 if (!p.num_points || !p.num_types) {
2844 WGR::wgr_path_release(p);
2845 return Nothing();
2847 return Some(QuantizedPath(p));
2850 // Get the output vertex buffer using WGR from an input quantized path.
2851 static Maybe<WGR::VertexBuffer> GeneratePathVertexBuffer(
2852 const QuantizedPath& aPath, const IntRect& aClipRect,
2853 bool aRasterizationTruncates, WGR::OutputVertex* aBuffer,
2854 size_t aBufferCapacity) {
2855 WGR::VertexBuffer vb = WGR::wgr_path_rasterize_to_tri_list(
2856 &aPath.mPath, aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height,
2857 true, false, aRasterizationTruncates, aBuffer, aBufferCapacity);
2858 if (!vb.len || (aBuffer && vb.len > aBufferCapacity)) {
2859 WGR::wgr_vertex_buffer_release(vb);
2860 return Nothing();
2862 return Some(vb);
2865 static inline AAStroke::LineJoin ToAAStrokeLineJoin(JoinStyle aJoin) {
2866 switch (aJoin) {
2867 case JoinStyle::BEVEL:
2868 return AAStroke::LineJoin::Bevel;
2869 case JoinStyle::ROUND:
2870 return AAStroke::LineJoin::Round;
2871 case JoinStyle::MITER:
2872 case JoinStyle::MITER_OR_BEVEL:
2873 return AAStroke::LineJoin::Miter;
2875 return AAStroke::LineJoin::Miter;
2878 static inline AAStroke::LineCap ToAAStrokeLineCap(CapStyle aCap) {
2879 switch (aCap) {
2880 case CapStyle::BUTT:
2881 return AAStroke::LineCap::Butt;
2882 case CapStyle::ROUND:
2883 return AAStroke::LineCap::Round;
2884 case CapStyle::SQUARE:
2885 return AAStroke::LineCap::Square;
2887 return AAStroke::LineCap::Butt;
2890 static inline Point WGRPointToPoint(const WGR::Point& aPoint) {
2891 return Point(IntPoint(aPoint.x, aPoint.y)) * (1.0f / 16.0f);
2894 // Generates a vertex buffer for a stroked path using aa-stroke.
2895 static Maybe<AAStroke::VertexBuffer> GenerateStrokeVertexBuffer(
2896 const QuantizedPath& aPath, const StrokeOptions* aStrokeOptions,
2897 float aScale, WGR::OutputVertex* aBuffer, size_t aBufferCapacity) {
2898 AAStroke::StrokeStyle style = {aStrokeOptions->mLineWidth * aScale,
2899 ToAAStrokeLineCap(aStrokeOptions->mLineCap),
2900 ToAAStrokeLineJoin(aStrokeOptions->mLineJoin),
2901 aStrokeOptions->mMiterLimit};
2902 if (style.width <= 0.0f || !std::isfinite(style.width) ||
2903 style.miter_limit <= 0.0f || !std::isfinite(style.miter_limit)) {
2904 return Nothing();
2906 AAStroke::Stroker* s = AAStroke::aa_stroke_new(
2907 &style, (AAStroke::OutputVertex*)aBuffer, aBufferCapacity);
2908 bool valid = true;
2909 size_t curPoint = 0;
2910 for (size_t curType = 0; valid && curType < aPath.mPath.num_types;) {
2911 // Verify that we are at the start of a sub-path.
2912 if ((aPath.mPath.types[curType] & WGR::PathPointTypePathTypeMask) !=
2913 WGR::PathPointTypeStart) {
2914 valid = false;
2915 break;
2917 // Find where the next sub-path starts so we can locate the end.
2918 size_t endType = curType + 1;
2919 for (; endType < aPath.mPath.num_types; endType++) {
2920 if ((aPath.mPath.types[endType] & WGR::PathPointTypePathTypeMask) ==
2921 WGR::PathPointTypeStart) {
2922 break;
2925 // Check if the path is closed. This is a flag modifying the last type.
2926 bool closed =
2927 (aPath.mPath.types[endType - 1] & WGR::PathPointTypeCloseSubpath) != 0;
2928 for (; curType < endType; curType++) {
2929 // If this is the last type and the sub-path is not closed, determine if
2930 // this segment should be capped.
2931 bool end = curType + 1 == endType && !closed;
2932 switch (aPath.mPath.types[curType] & WGR::PathPointTypePathTypeMask) {
2933 case WGR::PathPointTypeStart: {
2934 if (curPoint + 1 > aPath.mPath.num_points) {
2935 valid = false;
2936 break;
2938 Point p1 = WGRPointToPoint(aPath.mPath.points[curPoint]);
2939 AAStroke::aa_stroke_move_to(s, p1.x, p1.y, closed);
2940 if (end) {
2941 AAStroke::aa_stroke_line_to(s, p1.x, p1.y, true);
2943 curPoint++;
2944 break;
2946 case WGR::PathPointTypeLine: {
2947 if (curPoint + 1 > aPath.mPath.num_points) {
2948 valid = false;
2949 break;
2951 Point p1 = WGRPointToPoint(aPath.mPath.points[curPoint]);
2952 AAStroke::aa_stroke_line_to(s, p1.x, p1.y, end);
2953 curPoint++;
2954 break;
2956 case WGR::PathPointTypeBezier: {
2957 if (curPoint + 3 > aPath.mPath.num_points) {
2958 valid = false;
2959 break;
2961 Point p1 = WGRPointToPoint(aPath.mPath.points[curPoint]);
2962 Point p2 = WGRPointToPoint(aPath.mPath.points[curPoint + 1]);
2963 Point p3 = WGRPointToPoint(aPath.mPath.points[curPoint + 2]);
2964 AAStroke::aa_stroke_curve_to(s, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y,
2965 end);
2966 curPoint += 3;
2967 break;
2969 default:
2970 MOZ_ASSERT(false, "Unknown WGR path point type");
2971 valid = false;
2972 break;
2975 // Close the sub-path if necessary.
2976 if (valid && closed) {
2977 AAStroke::aa_stroke_close(s);
2980 Maybe<AAStroke::VertexBuffer> result;
2981 if (valid) {
2982 AAStroke::VertexBuffer vb = AAStroke::aa_stroke_finish(s);
2983 if (!vb.len || (aBuffer && vb.len > aBufferCapacity)) {
2984 AAStroke::aa_stroke_vertex_buffer_release(vb);
2985 } else {
2986 result = Some(vb);
2989 AAStroke::aa_stroke_release(s);
2990 return result;
2993 // Search the path cache for any entries stored in the path vertex buffer and
2994 // remove them.
2995 void PathCache::ClearVertexRanges() {
2996 for (auto& chain : mChains) {
2997 PathCacheEntry* entry = chain.getFirst();
2998 while (entry) {
2999 PathCacheEntry* next = entry->getNext();
3000 if (entry->GetVertexRange().IsValid()) {
3001 entry->Unlink();
3003 entry = next;
3008 inline bool DrawTargetWebgl::ShouldAccelPath(
3009 const DrawOptions& aOptions, const StrokeOptions* aStrokeOptions) {
3010 return mWebglValid && SupportsDrawOptions(aOptions) && PrepareContext();
3013 enum class AAStrokeMode {
3014 Unsupported,
3015 Geometry,
3016 Mask,
3019 // For now, we only directly support stroking solid color patterns to limit
3020 // artifacts from blending of overlapping geometry generated by AAStroke. Other
3021 // types of patterns may be partially supported by rendering to a temporary
3022 // mask.
3023 static inline AAStrokeMode SupportsAAStroke(const Pattern& aPattern,
3024 const DrawOptions& aOptions,
3025 const StrokeOptions& aStrokeOptions,
3026 bool aAllowStrokeAlpha) {
3027 if (aStrokeOptions.mDashPattern) {
3028 return AAStrokeMode::Unsupported;
3030 switch (aOptions.mCompositionOp) {
3031 case CompositionOp::OP_SOURCE:
3032 return AAStrokeMode::Geometry;
3033 case CompositionOp::OP_OVER:
3034 if (aPattern.GetType() == PatternType::COLOR) {
3035 return static_cast<const ColorPattern&>(aPattern).mColor.a *
3036 aOptions.mAlpha <
3037 1.0f &&
3038 !aAllowStrokeAlpha
3039 ? AAStrokeMode::Mask
3040 : AAStrokeMode::Geometry;
3042 return AAStrokeMode::Unsupported;
3043 default:
3044 return AAStrokeMode::Unsupported;
3048 // Render an AA-Stroke'd vertex range into an R8 mask texture for subsequent
3049 // drawing.
3050 already_AddRefed<TextureHandle> DrawTargetWebgl::SharedContext::DrawStrokeMask(
3051 const PathVertexRange& aVertexRange, const IntSize& aSize) {
3052 // Allocate a new texture handle to store the rendered mask.
3053 RefPtr<TextureHandle> handle =
3054 AllocateTextureHandle(SurfaceFormat::A8, aSize, true, true);
3055 if (!handle) {
3056 return nullptr;
3059 IntRect texBounds = handle->GetBounds();
3060 BackingTexture* backing = handle->GetBackingTexture();
3061 if (!backing->IsInitialized()) {
3062 // If the backing texture is uninitialized, it needs its sampling parameters
3063 // set for later use.
3064 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, backing->GetWebGLTexture());
3065 mWebgl->TexStorage2D(LOCAL_GL_TEXTURE_2D, 1, LOCAL_GL_R8,
3066 backing->GetSize().width, backing->GetSize().height);
3067 InitTexParameters(backing->GetWebGLTexture());
3068 ClearLastTexture();
3071 // Set up a scratch framebuffer to render to the appropriate sub-texture of
3072 // the backing texture.
3073 if (!mScratchFramebuffer) {
3074 mScratchFramebuffer = mWebgl->CreateFramebuffer();
3076 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mScratchFramebuffer);
3077 mWebgl->FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
3078 LOCAL_GL_TEXTURE_2D, backing->GetWebGLTexture(),
3080 mWebgl->Viewport(texBounds.x, texBounds.y, texBounds.width, texBounds.height);
3081 if (!backing->IsInitialized()) {
3082 backing->MarkInitialized();
3083 // If the backing texture is uninitialized, then clear the entire backing
3084 // texture to initialize it.
3085 DisableScissor();
3086 } else {
3087 // Clear only the sub-texture.
3088 EnableScissor(texBounds);
3090 // Ensure the mask background is clear.
3091 mWebgl->ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
3092 mWebgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT);
3094 // Reset any blending when drawing the mask.
3095 SetBlendState(CompositionOp::OP_OVER);
3097 // Set up the solid color shader to draw a simple opaque mask.
3098 if (mLastProgram != mSolidProgram) {
3099 mWebgl->UseProgram(mSolidProgram);
3100 mLastProgram = mSolidProgram;
3102 Array<float, 2> viewportData = {float(texBounds.width),
3103 float(texBounds.height)};
3104 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramViewport, viewportData,
3105 mSolidProgramUniformState.mViewport);
3106 Array<float, 1> aaData = {0.0f};
3107 MaybeUniformData(LOCAL_GL_FLOAT, mSolidProgramAA, aaData,
3108 mSolidProgramUniformState.mAA);
3109 Array<float, 4> clipData = {-0.5f, -0.5f, float(texBounds.width) + 0.5f,
3110 float(texBounds.height) + 0.5f};
3111 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramClipBounds, clipData,
3112 mSolidProgramUniformState.mClipBounds);
3113 Array<float, 4> colorData = {1.0f, 1.0f, 1.0f, 1.0f};
3114 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramColor, colorData,
3115 mSolidProgramUniformState.mColor);
3116 Array<float, 6> xformData = {1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f};
3117 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramTransform, xformData,
3118 mSolidProgramUniformState.mTransform);
3120 // Ensure the current clip mask is ignored.
3121 RefPtr<WebGLTextureJS> prevClipMask = mLastClipMask;
3122 SetNoClipMask();
3124 // Draw the mask using the supplied path vertex range.
3125 mWebgl->DrawArrays(LOCAL_GL_TRIANGLES, GLint(aVertexRange.mOffset),
3126 GLsizei(aVertexRange.mLength));
3128 // Restore the previous framebuffer state.
3129 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mCurrentTarget->mFramebuffer);
3130 mWebgl->Viewport(0, 0, mViewportSize.width, mViewportSize.height);
3131 if (prevClipMask) {
3132 SetClipMask(prevClipMask);
3135 return handle.forget();
3138 bool DrawTargetWebgl::SharedContext::DrawPathAccel(
3139 const Path* aPath, const Pattern& aPattern, const DrawOptions& aOptions,
3140 const StrokeOptions* aStrokeOptions, bool aAllowStrokeAlpha,
3141 const ShadowOptions* aShadow, bool aCacheable) {
3142 // Get the transformed bounds for the path and conservatively check if the
3143 // bounds overlap the canvas.
3144 const PathSkia* pathSkia = static_cast<const PathSkia*>(aPath);
3145 const Matrix& currentTransform = GetTransform();
3146 Rect bounds = pathSkia->GetFastBounds(currentTransform, aStrokeOptions);
3147 // If the path is empty, then there is nothing to draw.
3148 if (bounds.IsEmpty()) {
3149 return true;
3151 IntRect viewport(IntPoint(), mViewportSize);
3152 if (aShadow) {
3153 // Inflate the bounds to account for the blur radius.
3154 bounds += aShadow->mOffset;
3155 int32_t blurRadius = aShadow->BlurRadius();
3156 bounds.Inflate(blurRadius);
3157 viewport.Inflate(blurRadius);
3159 Point realOrigin = bounds.TopLeft();
3160 if (aCacheable) {
3161 // Quantize the path origin to increase the reuse of cache entries.
3162 bounds.Scale(4.0f);
3163 bounds.Round();
3164 bounds.Scale(0.25f);
3166 Point quantizedOrigin = bounds.TopLeft();
3167 // If the path doesn't intersect the viewport, then there is nothing to draw.
3168 IntRect intBounds = RoundedOut(bounds).Intersect(viewport);
3169 if (intBounds.IsEmpty()) {
3170 return true;
3172 // Nudge the bounds to account for the quantization rounding.
3173 Rect quantBounds = Rect(intBounds) + (realOrigin - quantizedOrigin);
3174 // If the pattern is a solid color, then this will be used along with a path
3175 // mask to render the path, as opposed to baking the pattern into the cached
3176 // path texture.
3177 Maybe<DeviceColor> color =
3178 aOptions.mCompositionOp == CompositionOp::OP_CLEAR
3179 ? Some(DeviceColor(1, 1, 1, 1))
3180 : (aPattern.GetType() == PatternType::COLOR
3181 ? Some(static_cast<const ColorPattern&>(aPattern).mColor)
3182 : Nothing());
3183 // Look for an existing path cache entry, if possible, or otherwise create
3184 // one. If the draw request is not cacheable, then don't create an entry.
3185 RefPtr<PathCacheEntry> entry;
3186 RefPtr<TextureHandle> handle;
3187 if (aCacheable) {
3188 if (!mPathCache) {
3189 mPathCache = MakeUnique<PathCache>();
3191 // Use a quantized, relative (to its bounds origin) version of the path as
3192 // a cache key to help limit cache bloat.
3193 Maybe<QuantizedPath> qp = GenerateQuantizedPath(
3194 pathSkia->GetPath(), quantBounds, currentTransform);
3195 if (!qp) {
3196 return false;
3198 entry = mPathCache->FindOrInsertEntry(
3199 std::move(*qp), color ? nullptr : &aPattern, aStrokeOptions,
3200 currentTransform, intBounds, quantizedOrigin,
3201 aShadow ? aShadow->mSigma : -1.0f);
3202 if (!entry) {
3203 return false;
3205 handle = entry->GetHandle();
3208 // If there is a shadow, it needs to draw with the shadow color rather than
3209 // the path color.
3210 Maybe<DeviceColor> shadowColor = color;
3211 if (aShadow && aOptions.mCompositionOp != CompositionOp::OP_CLEAR) {
3212 shadowColor = Some(aShadow->mColor);
3213 if (color) {
3214 shadowColor->a *= color->a;
3217 SamplingFilter filter =
3218 aShadow ? SamplingFilter::GOOD : GetSamplingFilter(aPattern);
3219 if (handle && handle->IsValid()) {
3220 // If the entry has a valid texture handle still, use it. However, the
3221 // entry texture is assumed to be located relative to its previous bounds.
3222 // We need to offset the pattern by the difference between its new unclipped
3223 // origin and its previous previous unclipped origin. Then when we finally
3224 // draw a rectangle at the expected new bounds, it will overlap the portion
3225 // of the old entry texture we actually need to sample from.
3226 Point offset =
3227 (realOrigin - entry->GetOrigin()) + entry->GetBounds().TopLeft();
3228 SurfacePattern pathPattern(nullptr, ExtendMode::CLAMP,
3229 Matrix::Translation(offset), filter);
3230 return DrawRectAccel(quantBounds, pathPattern, aOptions, shadowColor,
3231 &handle, false, true, true);
3234 if (mPathVertexCapacity > 0 && !handle && entry && !aShadow &&
3235 aOptions.mAntialiasMode != AntialiasMode::NONE &&
3236 SupportsPattern(aPattern) &&
3237 entry->GetPath().mPath.num_types <= mPathMaxComplexity) {
3238 if (entry->GetVertexRange().IsValid()) {
3239 // If there is a valid cached vertex data in the path vertex buffer, then
3240 // just draw that. We must draw at integer pixel boundaries (using
3241 // intBounds instead of quantBounds) due to WGR's reliance on pixel center
3242 // location.
3243 mCurrentTarget->mProfile.OnCacheHit();
3244 return DrawRectAccel(Rect(intBounds.TopLeft(), Size(1, 1)), aPattern,
3245 aOptions, Nothing(), nullptr, false, true, true,
3246 false, nullptr, &entry->GetVertexRange());
3249 // printf_stderr("Generating... verbs %d, points %d\n",
3250 // int(pathSkia->GetPath().countVerbs()),
3251 // int(pathSkia->GetPath().countPoints()));
3252 WGR::OutputVertex* outputBuffer = nullptr;
3253 size_t outputBufferCapacity = 0;
3254 if (mWGROutputBuffer) {
3255 outputBuffer = mWGROutputBuffer.get();
3256 outputBufferCapacity = mPathVertexCapacity / sizeof(WGR::OutputVertex);
3258 Maybe<WGR::VertexBuffer> wgrVB;
3259 Maybe<AAStroke::VertexBuffer> strokeVB;
3260 if (!aStrokeOptions) {
3261 wgrVB = GeneratePathVertexBuffer(
3262 entry->GetPath(), IntRect(-intBounds.TopLeft(), mViewportSize),
3263 mRasterizationTruncates, outputBuffer, outputBufferCapacity);
3264 } else {
3265 if (mPathAAStroke &&
3266 SupportsAAStroke(aPattern, aOptions, *aStrokeOptions,
3267 aAllowStrokeAlpha) != AAStrokeMode::Unsupported) {
3268 auto scaleFactors = currentTransform.ScaleFactors();
3269 if (scaleFactors.AreScalesSame()) {
3270 strokeVB = GenerateStrokeVertexBuffer(
3271 entry->GetPath(), aStrokeOptions, scaleFactors.xScale,
3272 outputBuffer, outputBufferCapacity);
3275 if (!strokeVB && mPathWGRStroke) {
3276 // If stroking, then generate a path to fill the stroked region. This
3277 // path will need to be quantized again because it differs from the
3278 // path used for the cache entry, but this allows us to avoid
3279 // generating a fill path on a cache hit.
3280 Maybe<Rect> cullRect;
3281 Matrix invTransform = currentTransform;
3282 if (invTransform.Invert()) {
3283 // Transform the stroking clip rect from device space to local
3284 // space.
3285 Rect invRect = invTransform.TransformBounds(Rect(mClipRect));
3286 invRect.RoundOut();
3287 cullRect = Some(invRect);
3289 SkPath fillPath;
3290 if (pathSkia->GetFillPath(*aStrokeOptions, currentTransform, fillPath,
3291 cullRect)) {
3292 // printf_stderr(" stroke fill... verbs %d, points %d\n",
3293 // int(fillPath.countVerbs()),
3294 // int(fillPath.countPoints()));
3295 if (Maybe<QuantizedPath> qp = GenerateQuantizedPath(
3296 fillPath, quantBounds, currentTransform)) {
3297 wgrVB = GeneratePathVertexBuffer(
3298 *qp, IntRect(-intBounds.TopLeft(), mViewportSize),
3299 mRasterizationTruncates, outputBuffer, outputBufferCapacity);
3304 if (wgrVB || strokeVB) {
3305 const uint8_t* vbData =
3306 wgrVB ? (const uint8_t*)wgrVB->data : (const uint8_t*)strokeVB->data;
3307 if (outputBuffer && !vbData) {
3308 vbData = (const uint8_t*)outputBuffer;
3310 size_t vbLen = wgrVB ? wgrVB->len : strokeVB->len;
3311 uint32_t vertexBytes = uint32_t(
3312 std::min(vbLen * sizeof(WGR::OutputVertex), size_t(UINT32_MAX)));
3313 // printf_stderr(" ... %d verts, %d bytes\n", int(vbLen),
3314 // int(vertexBytes));
3315 if (vertexBytes > mPathVertexCapacity - mPathVertexOffset &&
3316 vertexBytes <= mPathVertexCapacity - sizeof(kRectVertexData)) {
3317 // If the vertex data is too large to fit in the remaining path vertex
3318 // buffer, then orphan the contents of the vertex buffer to make room
3319 // for it.
3320 if (mPathCache) {
3321 mPathCache->ClearVertexRanges();
3323 ResetPathVertexBuffer(false);
3325 if (vertexBytes <= mPathVertexCapacity - mPathVertexOffset) {
3326 // If there is actually room to fit the vertex data in the vertex buffer
3327 // after orphaning as necessary, then upload the data to the next
3328 // available offset in the buffer.
3329 PathVertexRange vertexRange(
3330 uint32_t(mPathVertexOffset / sizeof(WGR::OutputVertex)),
3331 uint32_t(vbLen));
3332 // printf_stderr(" ... offset %d\n", mPathVertexOffset);
3333 // Normal glBufferSubData interleaved with draw calls causes performance
3334 // issues on Mali, so use our special unsynchronized version. This is
3335 // safe as we never update regions referenced by pending draw calls.
3336 mWebgl->RawBufferSubData(LOCAL_GL_ARRAY_BUFFER, mPathVertexOffset,
3337 vbData, vertexBytes,
3338 /* unsynchronized */ true);
3339 mPathVertexOffset += vertexBytes;
3340 if (wgrVB) {
3341 WGR::wgr_vertex_buffer_release(wgrVB.ref());
3342 } else {
3343 AAStroke::aa_stroke_vertex_buffer_release(strokeVB.ref());
3345 if (strokeVB &&
3346 SupportsAAStroke(aPattern, aOptions, *aStrokeOptions,
3347 aAllowStrokeAlpha) == AAStrokeMode::Mask) {
3348 // Attempt to generate a stroke mask for path.
3349 if (RefPtr<TextureHandle> handle =
3350 DrawStrokeMask(vertexRange, intBounds.Size())) {
3351 // Finally, draw the rendered stroke mask.
3352 if (entry) {
3353 entry->Link(handle);
3355 mCurrentTarget->mProfile.OnCacheMiss();
3356 SurfacePattern maskPattern(
3357 nullptr, ExtendMode::CLAMP,
3358 Matrix::Translation(quantBounds.TopLeft()),
3359 SamplingFilter::GOOD);
3360 return DrawRectAccel(quantBounds, maskPattern, aOptions, color,
3361 &handle, false, true, true);
3363 } else {
3364 // Remember the vertex range in the cache entry so that it can be
3365 // reused later.
3366 if (entry) {
3367 entry->SetVertexRange(vertexRange);
3370 // Finally, draw the uploaded vertex data.
3371 mCurrentTarget->mProfile.OnCacheMiss();
3372 return DrawRectAccel(Rect(intBounds.TopLeft(), Size(1, 1)), aPattern,
3373 aOptions, Nothing(), nullptr, false, true, true,
3374 false, nullptr, &vertexRange);
3376 } else {
3377 if (wgrVB) {
3378 WGR::wgr_vertex_buffer_release(wgrVB.ref());
3379 } else {
3380 AAStroke::aa_stroke_vertex_buffer_release(strokeVB.ref());
3383 // If we failed to draw the vertex data for some reason, then fall through
3384 // to the texture rasterization path.
3388 // If a stroke path covers too much screen area, it is likely that most is
3389 // empty space in the interior. This usually imposes too high a cost versus
3390 // just rasterizing without acceleration. Note that AA-Stroke generally
3391 // produces more acceptable amounts of geometry for larger paths, so we do
3392 // this heuristic after we attempt AA-Stroke.
3393 if (aStrokeOptions &&
3394 intBounds.width * intBounds.height >
3395 (mViewportSize.width / 2) * (mViewportSize.height / 2)) {
3396 return false;
3399 // If there isn't a valid texture handle, then we need to rasterize the
3400 // path in a software canvas and upload this to a texture. Solid color
3401 // patterns will be rendered as a path mask that can then be modulated
3402 // with any color. Other pattern types have to rasterize the pattern
3403 // directly into the cached texture.
3404 handle = nullptr;
3405 RefPtr<DrawTargetSkia> pathDT = new DrawTargetSkia;
3406 if (pathDT->Init(intBounds.Size(), color || aShadow
3407 ? SurfaceFormat::A8
3408 : SurfaceFormat::B8G8R8A8)) {
3409 Point offset = -quantBounds.TopLeft();
3410 if (aShadow) {
3411 // Ensure the the shadow is drawn at the requested offset
3412 offset += aShadow->mOffset;
3414 pathDT->SetTransform(currentTransform * Matrix::Translation(offset));
3415 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER,
3416 aOptions.mAntialiasMode);
3417 static const ColorPattern maskPattern(DeviceColor(1.0f, 1.0f, 1.0f, 1.0f));
3418 const Pattern& cachePattern = color ? maskPattern : aPattern;
3419 // If the source pattern is a DrawTargetWebgl snapshot, we may shift
3420 // targets when drawing the path, so back up the old target.
3421 DrawTargetWebgl* oldTarget = mCurrentTarget;
3422 if (aStrokeOptions) {
3423 pathDT->Stroke(aPath, cachePattern, *aStrokeOptions, drawOptions);
3424 } else {
3425 pathDT->Fill(aPath, cachePattern, drawOptions);
3427 if (aShadow && aShadow->mSigma > 0.0f) {
3428 // Blur the shadow if required.
3429 uint8_t* data = nullptr;
3430 IntSize size;
3431 int32_t stride = 0;
3432 SurfaceFormat format = SurfaceFormat::UNKNOWN;
3433 if (pathDT->LockBits(&data, &size, &stride, &format)) {
3434 AlphaBoxBlur blur(Rect(pathDT->GetRect()), stride, aShadow->mSigma,
3435 aShadow->mSigma);
3436 blur.Blur(data);
3437 pathDT->ReleaseBits(data);
3440 RefPtr<SourceSurface> pathSurface = pathDT->Snapshot();
3441 if (pathSurface) {
3442 // If the target changed, try to restore it.
3443 if (mCurrentTarget != oldTarget && !oldTarget->PrepareContext()) {
3444 return false;
3446 SurfacePattern pathPattern(pathSurface, ExtendMode::CLAMP,
3447 Matrix::Translation(quantBounds.TopLeft()),
3448 filter);
3449 // Try and upload the rasterized path to a texture. If there is a
3450 // valid texture handle after this, then link it to the entry.
3451 // Otherwise, we might have to fall back to software drawing the
3452 // path, so unlink it from the entry.
3453 if (DrawRectAccel(quantBounds, pathPattern, aOptions, shadowColor,
3454 &handle, false, true) &&
3455 handle) {
3456 if (entry) {
3457 entry->Link(handle);
3459 } else if (entry) {
3460 entry->Unlink();
3462 return true;
3466 return false;
3469 void DrawTargetWebgl::DrawPath(const Path* aPath, const Pattern& aPattern,
3470 const DrawOptions& aOptions,
3471 const StrokeOptions* aStrokeOptions,
3472 bool aAllowStrokeAlpha) {
3473 // If there is a WebGL context, then try to cache the path to avoid slow
3474 // fallbacks.
3475 if (ShouldAccelPath(aOptions, aStrokeOptions) &&
3476 mSharedContext->DrawPathAccel(aPath, aPattern, aOptions, aStrokeOptions,
3477 aAllowStrokeAlpha)) {
3478 return;
3481 // There was no path cache entry available to use, so fall back to drawing the
3482 // path with Skia.
3483 MarkSkiaChanged(aOptions);
3484 if (aStrokeOptions) {
3485 mSkia->Stroke(aPath, aPattern, *aStrokeOptions, aOptions);
3486 } else {
3487 mSkia->Fill(aPath, aPattern, aOptions);
3491 void DrawTargetWebgl::DrawSurface(SourceSurface* aSurface, const Rect& aDest,
3492 const Rect& aSource,
3493 const DrawSurfaceOptions& aSurfOptions,
3494 const DrawOptions& aOptions) {
3495 Matrix matrix = Matrix::Scaling(aDest.width / aSource.width,
3496 aDest.height / aSource.height);
3497 matrix.PreTranslate(-aSource.x, -aSource.y);
3498 matrix.PostTranslate(aDest.x, aDest.y);
3499 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, matrix,
3500 aSurfOptions.mSamplingFilter);
3501 DrawRect(aDest, pattern, aOptions);
3504 void DrawTargetWebgl::Mask(const Pattern& aSource, const Pattern& aMask,
3505 const DrawOptions& aOptions) {
3506 if (!SupportsDrawOptions(aOptions) ||
3507 aMask.GetType() != PatternType::SURFACE ||
3508 aSource.GetType() != PatternType::COLOR) {
3509 MarkSkiaChanged(aOptions);
3510 mSkia->Mask(aSource, aMask, aOptions);
3511 return;
3513 auto sourceColor = static_cast<const ColorPattern&>(aSource).mColor;
3514 auto maskPattern = static_cast<const SurfacePattern&>(aMask);
3515 DrawRect(Rect(IntRect(IntPoint(), maskPattern.mSurface->GetSize())),
3516 maskPattern, aOptions, Some(sourceColor));
3519 void DrawTargetWebgl::MaskSurface(const Pattern& aSource, SourceSurface* aMask,
3520 Point aOffset, const DrawOptions& aOptions) {
3521 if (!SupportsDrawOptions(aOptions) ||
3522 aSource.GetType() != PatternType::COLOR) {
3523 MarkSkiaChanged(aOptions);
3524 mSkia->MaskSurface(aSource, aMask, aOffset, aOptions);
3525 } else {
3526 auto sourceColor = static_cast<const ColorPattern&>(aSource).mColor;
3527 SurfacePattern pattern(aMask, ExtendMode::CLAMP,
3528 Matrix::Translation(aOffset));
3529 DrawRect(Rect(aOffset, Size(aMask->GetSize())), pattern, aOptions,
3530 Some(sourceColor));
3534 // Extract the surface's alpha values into an A8 surface.
3535 static already_AddRefed<DataSourceSurface> ExtractAlpha(SourceSurface* aSurface,
3536 bool aAllowSubpixelAA) {
3537 RefPtr<DataSourceSurface> surfaceData = aSurface->GetDataSurface();
3538 if (!surfaceData) {
3539 return nullptr;
3541 DataSourceSurface::ScopedMap srcMap(surfaceData, DataSourceSurface::READ);
3542 if (!srcMap.IsMapped()) {
3543 return nullptr;
3545 IntSize size = surfaceData->GetSize();
3546 RefPtr<DataSourceSurface> alpha =
3547 Factory::CreateDataSourceSurface(size, SurfaceFormat::A8, false);
3548 if (!alpha) {
3549 return nullptr;
3551 DataSourceSurface::ScopedMap dstMap(alpha, DataSourceSurface::WRITE);
3552 if (!dstMap.IsMapped()) {
3553 return nullptr;
3555 // For subpixel masks, ignore the alpha and instead sample one of the color
3556 // channels as if they were alpha.
3557 SwizzleData(
3558 srcMap.GetData(), srcMap.GetStride(),
3559 aAllowSubpixelAA ? SurfaceFormat::A8R8G8B8 : surfaceData->GetFormat(),
3560 dstMap.GetData(), dstMap.GetStride(), SurfaceFormat::A8, size);
3561 return alpha.forget();
3564 void DrawTargetWebgl::DrawShadow(const Path* aPath, const Pattern& aPattern,
3565 const ShadowOptions& aShadow,
3566 const DrawOptions& aOptions,
3567 const StrokeOptions* aStrokeOptions) {
3568 // If there is a WebGL context, then try to cache the path to avoid slow
3569 // fallbacks.
3570 if (ShouldAccelPath(aOptions, aStrokeOptions) &&
3571 mSharedContext->DrawPathAccel(aPath, aPattern, aOptions, aStrokeOptions,
3572 false, &aShadow)) {
3573 return;
3576 // There was no path cache entry available to use, so fall back to drawing the
3577 // path with Skia.
3578 MarkSkiaChanged(aOptions);
3579 mSkia->DrawShadow(aPath, aPattern, aShadow, aOptions, aStrokeOptions);
3582 void DrawTargetWebgl::DrawSurfaceWithShadow(SourceSurface* aSurface,
3583 const Point& aDest,
3584 const ShadowOptions& aShadow,
3585 CompositionOp aOperator) {
3586 DrawOptions options(1.0f, aOperator);
3587 if (ShouldAccelPath(options, nullptr)) {
3588 SurfacePattern pattern(aSurface, ExtendMode::CLAMP,
3589 Matrix::Translation(aDest));
3590 SkPath skiaPath;
3591 skiaPath.addRect(RectToSkRect(Rect(aSurface->GetRect()) + aDest));
3592 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
3593 AutoRestoreTransform restore(this);
3594 SetTransform(Matrix());
3595 if (mSharedContext->DrawPathAccel(path, pattern, options, nullptr, false,
3596 &aShadow, false)) {
3597 DrawRect(Rect(aSurface->GetRect()) + aDest, pattern, options);
3598 return;
3602 MarkSkiaChanged(options);
3603 mSkia->DrawSurfaceWithShadow(aSurface, aDest, aShadow, aOperator);
3606 already_AddRefed<PathBuilder> DrawTargetWebgl::CreatePathBuilder(
3607 FillRule aFillRule) const {
3608 return mSkia->CreatePathBuilder(aFillRule);
3611 void DrawTargetWebgl::SetTransform(const Matrix& aTransform) {
3612 DrawTarget::SetTransform(aTransform);
3613 mSkia->SetTransform(aTransform);
3616 void DrawTargetWebgl::StrokeRect(const Rect& aRect, const Pattern& aPattern,
3617 const StrokeOptions& aStrokeOptions,
3618 const DrawOptions& aOptions) {
3619 if (!mWebglValid) {
3620 MarkSkiaChanged(aOptions);
3621 mSkia->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions);
3622 } else {
3623 // If the stroke options are unsupported, then transform the rect to a path
3624 // so it can be cached.
3625 SkPath skiaPath;
3626 skiaPath.addRect(RectToSkRect(aRect));
3627 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
3628 DrawPath(path, aPattern, aOptions, &aStrokeOptions, true);
3632 static inline bool IsThinLine(const Matrix& aTransform,
3633 const StrokeOptions& aStrokeOptions) {
3634 auto scale = aTransform.ScaleFactors();
3635 return std::max(scale.xScale, scale.yScale) * aStrokeOptions.mLineWidth <= 1;
3638 bool DrawTargetWebgl::StrokeLineAccel(const Point& aStart, const Point& aEnd,
3639 const Pattern& aPattern,
3640 const StrokeOptions& aStrokeOptions,
3641 const DrawOptions& aOptions,
3642 bool aClosed) {
3643 // Approximating a wide line as a rectangle works only with certain cap styles
3644 // in the general case (butt or square). However, if the line width is
3645 // sufficiently thin, we can either ignore the round cap (or treat it like
3646 // square for zero-length lines) without causing objectionable artifacts.
3647 // Lines may sometimes be used in closed paths that immediately reverse back,
3648 // in which case we need to use mLineJoin instead of mLineCap to determine the
3649 // actual cap used.
3650 CapStyle capStyle =
3651 aClosed ? (aStrokeOptions.mLineJoin == JoinStyle::ROUND ? CapStyle::ROUND
3652 : CapStyle::BUTT)
3653 : aStrokeOptions.mLineCap;
3654 if (mWebglValid && SupportsPattern(aPattern) &&
3655 (capStyle != CapStyle::ROUND ||
3656 IsThinLine(GetTransform(), aStrokeOptions)) &&
3657 aStrokeOptions.mDashPattern == nullptr && aStrokeOptions.mLineWidth > 0) {
3658 // Treat the line as a rectangle whose center-line is the supplied line and
3659 // for which the height is the supplied line width. Generate a matrix that
3660 // maps the X axis to the orientation of the line and the Y axis to the
3661 // normal vector to the line. This only works if the line caps are squared,
3662 // as rounded rectangles are currently not supported for round line caps.
3663 Point start = aStart;
3664 Point dirX = aEnd - aStart;
3665 Point dirY;
3666 float dirLen = dirX.Length();
3667 float scale = aStrokeOptions.mLineWidth;
3668 if (dirLen == 0.0f) {
3669 // If the line is zero-length, then only a cap is rendered.
3670 switch (capStyle) {
3671 case CapStyle::BUTT:
3672 // The cap doesn't extend beyond the line so nothing is drawn.
3673 return true;
3674 case CapStyle::ROUND:
3675 case CapStyle::SQUARE:
3676 // Draw a unit square centered at the single point.
3677 dirX = Point(scale, 0.0f);
3678 dirY = Point(0.0f, scale);
3679 // Offset the start by half a unit.
3680 start.x -= 0.5f * scale;
3681 break;
3683 } else {
3684 // Make the scale map to a single unit length.
3685 scale /= dirLen;
3686 dirY = Point(-dirX.y, dirX.x) * scale;
3687 if (capStyle == CapStyle::SQUARE) {
3688 // Offset the start by half a unit.
3689 start -= (dirX * scale) * 0.5f;
3690 // Ensure the extent also accounts for the start and end cap.
3691 dirX += dirX * scale;
3694 Matrix lineXform(dirX.x, dirX.y, dirY.x, dirY.y, start.x - 0.5f * dirY.x,
3695 start.y - 0.5f * dirY.y);
3696 AutoRestoreTransform restore(this);
3697 ConcatTransform(lineXform);
3698 if (DrawRect(Rect(0, 0, 1, 1), aPattern, aOptions, Nothing(), nullptr, true,
3699 true, true)) {
3700 return true;
3703 return false;
3706 void DrawTargetWebgl::StrokeLine(const Point& aStart, const Point& aEnd,
3707 const Pattern& aPattern,
3708 const StrokeOptions& aStrokeOptions,
3709 const DrawOptions& aOptions) {
3710 if (!mWebglValid) {
3711 MarkSkiaChanged(aOptions);
3712 mSkia->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions);
3713 } else if (!StrokeLineAccel(aStart, aEnd, aPattern, aStrokeOptions,
3714 aOptions)) {
3715 // If the stroke options are unsupported, then transform the line to a path
3716 // so it can be cached.
3717 SkPath skiaPath;
3718 skiaPath.moveTo(PointToSkPoint(aStart));
3719 skiaPath.lineTo(PointToSkPoint(aEnd));
3720 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING);
3721 DrawPath(path, aPattern, aOptions, &aStrokeOptions, true);
3725 void DrawTargetWebgl::Stroke(const Path* aPath, const Pattern& aPattern,
3726 const StrokeOptions& aStrokeOptions,
3727 const DrawOptions& aOptions) {
3728 if (!aPath || aPath->GetBackendType() != BackendType::SKIA) {
3729 return;
3731 const auto& skiaPath = static_cast<const PathSkia*>(aPath)->GetPath();
3732 if (!mWebglValid) {
3733 MarkSkiaChanged(aOptions);
3734 mSkia->Stroke(aPath, aPattern, aStrokeOptions, aOptions);
3735 return;
3738 // Avoid using Skia's isLine here because some paths erroneously include a
3739 // closePath at the end, causing isLine to not detect the line. In that case
3740 // we just draw a line in reverse right over the original line.
3741 int numVerbs = skiaPath.countVerbs();
3742 bool allowStrokeAlpha = false;
3743 if (numVerbs >= 2 && numVerbs <= 3) {
3744 uint8_t verbs[3];
3745 skiaPath.getVerbs(verbs, numVerbs);
3746 if (verbs[0] == SkPath::kMove_Verb && verbs[1] == SkPath::kLine_Verb &&
3747 (numVerbs < 3 || verbs[2] == SkPath::kClose_Verb)) {
3748 bool closed = numVerbs >= 3;
3749 Point start = SkPointToPoint(skiaPath.getPoint(0));
3750 Point end = SkPointToPoint(skiaPath.getPoint(1));
3751 if (StrokeLineAccel(start, end, aPattern, aStrokeOptions, aOptions,
3752 closed)) {
3753 if (closed) {
3754 StrokeLineAccel(end, start, aPattern, aStrokeOptions, aOptions, true);
3756 return;
3758 // If accelerated line drawing failed, just treat it as a path.
3759 allowStrokeAlpha = true;
3763 DrawPath(aPath, aPattern, aOptions, &aStrokeOptions, allowStrokeAlpha);
3766 bool DrawTargetWebgl::ShouldUseSubpixelAA(ScaledFont* aFont,
3767 const DrawOptions& aOptions) {
3768 AntialiasMode aaMode = aFont->GetDefaultAAMode();
3769 if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
3770 aaMode = aOptions.mAntialiasMode;
3772 return GetPermitSubpixelAA() &&
3773 (aaMode == AntialiasMode::DEFAULT ||
3774 aaMode == AntialiasMode::SUBPIXEL) &&
3775 aOptions.mCompositionOp == CompositionOp::OP_OVER;
3778 void DrawTargetWebgl::StrokeGlyphs(ScaledFont* aFont,
3779 const GlyphBuffer& aBuffer,
3780 const Pattern& aPattern,
3781 const StrokeOptions& aStrokeOptions,
3782 const DrawOptions& aOptions) {
3783 if (!aFont || !aBuffer.mNumGlyphs) {
3784 return;
3787 bool useSubpixelAA = ShouldUseSubpixelAA(aFont, aOptions);
3789 if (mWebglValid && SupportsDrawOptions(aOptions) &&
3790 aPattern.GetType() == PatternType::COLOR && PrepareContext() &&
3791 mSharedContext->DrawGlyphsAccel(aFont, aBuffer, aPattern, aOptions,
3792 &aStrokeOptions, useSubpixelAA)) {
3793 return;
3796 if (useSubpixelAA) {
3797 // Subpixel AA does not support layering because the subpixel masks can't
3798 // blend with the over op.
3799 MarkSkiaChanged();
3800 } else {
3801 MarkSkiaChanged(aOptions);
3803 mSkia->StrokeGlyphs(aFont, aBuffer, aPattern, aStrokeOptions, aOptions);
3806 // Depending on whether we enable subpixel position for a given font, Skia may
3807 // round transformed coordinates differently on each axis. By default, text is
3808 // subpixel quantized horizontally and snapped to a whole integer vertical
3809 // baseline. Axis-flip transforms instead snap to horizontal boundaries while
3810 // subpixel quantizing along the vertical. For other types of transforms, Skia
3811 // just applies subpixel quantization to both axes.
3812 // We must duplicate the amount of quantization Skia applies carefully as a
3813 // boundary value such as 0.49 may round to 0.5 with subpixel quantization,
3814 // but if Skia actually snapped it to a whole integer instead, it would round
3815 // down to 0. If a subsequent glyph with offset 0.51 came in, we might
3816 // mistakenly round it down to 0.5, whereas Skia would round it up to 1. Thus
3817 // we would alias 0.49 and 0.51 to the same cache entry, while Skia would
3818 // actually snap the offset to 0 or 1, depending, resulting in mismatched
3819 // hinting.
3820 static inline IntPoint QuantizeScale(ScaledFont* aFont,
3821 const Matrix& aTransform) {
3822 if (!aFont->UseSubpixelPosition()) {
3823 return {1, 1};
3825 if (aTransform._12 == 0) {
3826 // Glyphs are rendered subpixel horizontally, so snap vertically.
3827 return {4, 1};
3829 if (aTransform._11 == 0) {
3830 // Glyphs are rendered subpixel vertically, so snap horizontally.
3831 return {1, 4};
3833 // The transform isn't aligned, so don't snap.
3834 return {4, 4};
3837 // Skia only supports subpixel positioning to the nearest 1/4 fraction. It
3838 // would be wasteful to attempt to cache text runs with positioning that is
3839 // anymore precise than this. To prevent this cache bloat, we quantize the
3840 // transformed glyph positions to the nearest 1/4. The scaling factor for
3841 // the quantization is baked into the transform, so that if subpixel rounding
3842 // is used on a given axis, then the axis will be multiplied by 4 before
3843 // rounding. Since the quantized position is not used for rasterization, the
3844 // transform is safe to modify as such.
3845 static inline IntPoint QuantizePosition(const Matrix& aTransform,
3846 const IntPoint& aOffset,
3847 const Point& aPosition) {
3848 return RoundedToInt(aTransform.TransformPoint(aPosition)) - aOffset;
3851 // Get a quantized starting offset for the glyph buffer. We want this offset
3852 // to encapsulate the transform and buffer offset while still preserving the
3853 // relative subpixel positions of the glyphs this offset is subtracted from.
3854 static inline IntPoint QuantizeOffset(const Matrix& aTransform,
3855 const IntPoint& aQuantizeScale,
3856 const GlyphBuffer& aBuffer) {
3857 IntPoint offset =
3858 RoundedToInt(aTransform.TransformPoint(aBuffer.mGlyphs[0].mPosition));
3859 offset.x.value &= ~(aQuantizeScale.x.value - 1);
3860 offset.y.value &= ~(aQuantizeScale.y.value - 1);
3861 return offset;
3864 // Hashes a glyph buffer to a single hash value that can be used for quick
3865 // comparisons. Each glyph position is transformed and quantized before
3866 // hashing.
3867 HashNumber GlyphCacheEntry::HashGlyphs(const GlyphBuffer& aBuffer,
3868 const Matrix& aTransform,
3869 const IntPoint& aQuantizeScale) {
3870 HashNumber hash = 0;
3871 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer);
3872 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) {
3873 const Glyph& glyph = aBuffer.mGlyphs[i];
3874 hash = AddToHash(hash, glyph.mIndex);
3875 IntPoint pos = QuantizePosition(aTransform, offset, glyph.mPosition);
3876 hash = AddToHash(hash, pos.x);
3877 hash = AddToHash(hash, pos.y);
3879 return hash;
3882 // Determines if an existing glyph cache entry matches an incoming text run.
3883 inline bool GlyphCacheEntry::MatchesGlyphs(
3884 const GlyphBuffer& aBuffer, const DeviceColor& aColor,
3885 const Matrix& aTransform, const IntPoint& aQuantizeOffset,
3886 const IntPoint& aBoundsOffset, const IntRect& aClipRect, HashNumber aHash,
3887 const StrokeOptions* aStrokeOptions) {
3888 // First check if the hash matches to quickly reject the text run before any
3889 // more expensive checking. If it matches, then check if the color and
3890 // transform are the same.
3891 if (aHash != mHash || aBuffer.mNumGlyphs != mBuffer.mNumGlyphs ||
3892 aColor != mColor || !HasMatchingScale(aTransform, mTransform)) {
3893 return false;
3895 // Finally check if all glyphs and their quantized positions match.
3896 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) {
3897 const Glyph& dst = mBuffer.mGlyphs[i];
3898 const Glyph& src = aBuffer.mGlyphs[i];
3899 if (dst.mIndex != src.mIndex ||
3900 dst.mPosition != Point(QuantizePosition(aTransform, aQuantizeOffset,
3901 src.mPosition))) {
3902 return false;
3905 // Check that stroke options actually match.
3906 if (aStrokeOptions) {
3907 // If stroking, verify that the entry is also stroked with the same options.
3908 if (!(mStrokeOptions && *aStrokeOptions == *mStrokeOptions)) {
3909 return false;
3911 } else if (mStrokeOptions) {
3912 // If not stroking, check if the entry is stroked. If so, don't match.
3913 return false;
3915 // Verify that the full bounds, once translated and clipped, are equal to the
3916 // clipped bounds.
3917 return (mFullBounds + aBoundsOffset)
3918 .Intersect(aClipRect)
3919 .IsEqualEdges(GetBounds() + aBoundsOffset);
3922 GlyphCacheEntry::GlyphCacheEntry(const GlyphBuffer& aBuffer,
3923 const DeviceColor& aColor,
3924 const Matrix& aTransform,
3925 const IntPoint& aQuantizeScale,
3926 const IntRect& aBounds,
3927 const IntRect& aFullBounds, HashNumber aHash,
3928 StoredStrokeOptions* aStrokeOptions)
3929 : CacheEntryImpl<GlyphCacheEntry>(aTransform, aBounds, aHash),
3930 mColor(aColor),
3931 mFullBounds(aFullBounds),
3932 mStrokeOptions(aStrokeOptions) {
3933 // Store a copy of the glyph buffer with positions already quantized for fast
3934 // comparison later.
3935 Glyph* glyphs = new Glyph[aBuffer.mNumGlyphs];
3936 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer);
3937 // Make the bounds relative to the offset so we can add a new offset later.
3938 IntPoint boundsOffset(offset.x / aQuantizeScale.x,
3939 offset.y / aQuantizeScale.y);
3940 mBounds -= boundsOffset;
3941 mFullBounds -= boundsOffset;
3942 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) {
3943 Glyph& dst = glyphs[i];
3944 const Glyph& src = aBuffer.mGlyphs[i];
3945 dst.mIndex = src.mIndex;
3946 dst.mPosition = Point(QuantizePosition(aTransform, offset, src.mPosition));
3948 mBuffer.mGlyphs = glyphs;
3949 mBuffer.mNumGlyphs = aBuffer.mNumGlyphs;
3952 GlyphCacheEntry::~GlyphCacheEntry() { delete[] mBuffer.mGlyphs; }
3954 // Attempt to find a matching entry in the glyph cache. The caller should check
3955 // whether the contained texture handle is valid to determine if it will need to
3956 // render the text run or just reuse the cached texture.
3957 already_AddRefed<GlyphCacheEntry> GlyphCache::FindEntry(
3958 const GlyphBuffer& aBuffer, const DeviceColor& aColor,
3959 const Matrix& aTransform, const IntPoint& aQuantizeScale,
3960 const IntRect& aClipRect, HashNumber aHash,
3961 const StrokeOptions* aStrokeOptions) {
3962 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer);
3963 IntPoint boundsOffset(offset.x / aQuantizeScale.x,
3964 offset.y / aQuantizeScale.y);
3965 for (const RefPtr<GlyphCacheEntry>& entry : GetChain(aHash)) {
3966 if (entry->MatchesGlyphs(aBuffer, aColor, aTransform, offset, boundsOffset,
3967 aClipRect, aHash, aStrokeOptions)) {
3968 return do_AddRef(entry);
3971 return nullptr;
3974 // Insert a new entry in the glyph cache.
3975 already_AddRefed<GlyphCacheEntry> GlyphCache::InsertEntry(
3976 const GlyphBuffer& aBuffer, const DeviceColor& aColor,
3977 const Matrix& aTransform, const IntPoint& aQuantizeScale,
3978 const IntRect& aBounds, const IntRect& aFullBounds, HashNumber aHash,
3979 const StrokeOptions* aStrokeOptions) {
3980 StoredStrokeOptions* strokeOptions = nullptr;
3981 if (aStrokeOptions) {
3982 strokeOptions = aStrokeOptions->Clone();
3983 if (!strokeOptions) {
3984 return nullptr;
3987 RefPtr<GlyphCacheEntry> entry =
3988 new GlyphCacheEntry(aBuffer, aColor, aTransform, aQuantizeScale, aBounds,
3989 aFullBounds, aHash, strokeOptions);
3990 Insert(entry);
3991 return entry.forget();
3994 GlyphCache::GlyphCache(ScaledFont* aFont) : mFont(aFont) {}
3996 static void ReleaseGlyphCache(void* aPtr) {
3997 delete static_cast<GlyphCache*>(aPtr);
4000 void DrawTargetWebgl::SetPermitSubpixelAA(bool aPermitSubpixelAA) {
4001 DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA);
4002 mSkia->SetPermitSubpixelAA(aPermitSubpixelAA);
4005 // Check for any color glyphs contained within a rasterized BGRA8 text result.
4006 static bool CheckForColorGlyphs(const RefPtr<SourceSurface>& aSurface) {
4007 if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) {
4008 return false;
4010 RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface();
4011 if (!dataSurf) {
4012 return true;
4014 DataSourceSurface::ScopedMap map(dataSurf, DataSourceSurface::READ);
4015 if (!map.IsMapped()) {
4016 return true;
4018 IntSize size = dataSurf->GetSize();
4019 const uint8_t* data = map.GetData();
4020 int32_t stride = map.GetStride();
4021 for (int y = 0; y < size.height; y++) {
4022 const uint32_t* x = (const uint32_t*)data;
4023 const uint32_t* end = x + size.width;
4024 for (; x < end; x++) {
4025 // Verify if all components are the same as for premultiplied grayscale.
4026 uint32_t color = *x;
4027 uint32_t gray = color & 0xFF;
4028 gray |= gray << 8;
4029 gray |= gray << 16;
4030 if (color != gray) return true;
4032 data += stride;
4034 return false;
4037 // Draws glyphs to the WebGL target by trying to generate a cached texture for
4038 // the text run that can be subsequently reused to quickly render the text run
4039 // without using any software surfaces.
4040 bool DrawTargetWebgl::SharedContext::DrawGlyphsAccel(
4041 ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern,
4042 const DrawOptions& aOptions, const StrokeOptions* aStrokeOptions,
4043 bool aUseSubpixelAA) {
4044 // Whether the font may use bitmaps. If so, we need to render the glyphs with
4045 // color as grayscale bitmaps will use the color while color emoji will not,
4046 // with no easy way to know ahead of time. We currently have to check the
4047 // rasterized result to see if there are any color glyphs. To render subpixel
4048 // masks, we need to know that the rasterized result actually represents a
4049 // subpixel mask rather than try to interpret it as a normal RGBA result such
4050 // as for color emoji.
4051 bool useBitmaps = !aStrokeOptions && aFont->MayUseBitmaps() &&
4052 aOptions.mCompositionOp != CompositionOp::OP_CLEAR;
4054 // Look for an existing glyph cache on the font. If not there, create it.
4055 GlyphCache* cache =
4056 static_cast<GlyphCache*>(aFont->GetUserData(&mGlyphCacheKey));
4057 if (!cache) {
4058 cache = new GlyphCache(aFont);
4059 aFont->AddUserData(&mGlyphCacheKey, cache, ReleaseGlyphCache);
4060 mGlyphCaches.insertFront(cache);
4062 // Hash the incoming text run and looking for a matching entry.
4063 DeviceColor color = aOptions.mCompositionOp == CompositionOp::OP_CLEAR
4064 ? DeviceColor(1, 1, 1, 1)
4065 : static_cast<const ColorPattern&>(aPattern).mColor;
4066 #ifdef XP_MACOSX
4067 // On macOS, depending on whether the text is classified as light-on-dark or
4068 // dark-on-light, we may end up with different amounts of dilation applied, so
4069 // we can't use the same mask in the two circumstances, or the glyphs will be
4070 // dilated incorrectly.
4071 bool lightOnDark =
4072 useBitmaps || (color.r >= 0.33f && color.g >= 0.33f && color.b >= 0.33f &&
4073 color.r + color.g + color.b >= 2.0f);
4074 #else
4075 // On other platforms, we assume no color-dependent dilation.
4076 const bool lightOnDark = true;
4077 #endif
4078 // If the font has bitmaps, use the color directly. Otherwise, the texture
4079 // will hold a grayscale mask, so encode the key's subpixel and light-or-dark
4080 // state in the color.
4081 const Matrix& currentTransform = GetTransform();
4082 IntPoint quantizeScale = QuantizeScale(aFont, currentTransform);
4083 Matrix quantizeTransform = currentTransform;
4084 quantizeTransform.PostScale(quantizeScale.x, quantizeScale.y);
4085 HashNumber hash =
4086 GlyphCacheEntry::HashGlyphs(aBuffer, quantizeTransform, quantizeScale);
4087 DeviceColor colorOrMask =
4088 useBitmaps
4089 ? color
4090 : DeviceColor::Mask(aUseSubpixelAA ? 1 : 0, lightOnDark ? 1 : 0);
4091 IntRect clipRect(IntPoint(), mViewportSize);
4092 RefPtr<GlyphCacheEntry> entry =
4093 cache->FindEntry(aBuffer, colorOrMask, quantizeTransform, quantizeScale,
4094 clipRect, hash, aStrokeOptions);
4095 if (!entry) {
4096 // For small text runs, bounds computations can be expensive relative to the
4097 // cost of looking up a cache result. Avoid doing local bounds computations
4098 // until actually inserting the entry into the cache.
4099 Maybe<Rect> bounds = mCurrentTarget->mSkia->GetGlyphLocalBounds(
4100 aFont, aBuffer, aPattern, aStrokeOptions, aOptions);
4101 if (!bounds) {
4102 return true;
4104 // Transform the local bounds into device space so that we know how big
4105 // the cached texture will be.
4106 Rect xformBounds = currentTransform.TransformBounds(*bounds);
4107 // Check if the transform flattens out the bounds before rounding.
4108 if (xformBounds.IsEmpty()) {
4109 return true;
4111 IntRect fullBounds = RoundedOut(currentTransform.TransformBounds(*bounds));
4112 IntRect clipBounds = fullBounds.Intersect(clipRect);
4113 // Check if the bounds are completely clipped out.
4114 if (clipBounds.IsEmpty()) {
4115 return true;
4117 entry = cache->InsertEntry(aBuffer, colorOrMask, quantizeTransform,
4118 quantizeScale, clipBounds, fullBounds, hash,
4119 aStrokeOptions);
4120 if (!entry) {
4121 return false;
4125 // The bounds of the entry may have a different transform offset from the
4126 // bounds of the currently drawn text run. The entry bounds are relative to
4127 // the entry's quantized offset already, so just move the bounds to the new
4128 // offset.
4129 IntRect intBounds = entry->GetBounds();
4130 IntPoint newOffset =
4131 QuantizeOffset(quantizeTransform, quantizeScale, aBuffer);
4132 intBounds +=
4133 IntPoint(newOffset.x / quantizeScale.x, newOffset.y / quantizeScale.y);
4134 // Ensure there is a clear border around the text. This must be applied only
4135 // after clipping so that we always have some border texels for filtering.
4136 intBounds.Inflate(2);
4138 RefPtr<TextureHandle> handle = entry->GetHandle();
4139 if (handle && handle->IsValid()) {
4140 // If there is an entry with a valid cached texture handle, then try
4141 // to draw with that. If that for some reason failed, then fall back
4142 // to using the Skia target as that means we were preventing from
4143 // drawing to the WebGL context based on something other than the
4144 // texture.
4145 SurfacePattern pattern(nullptr, ExtendMode::CLAMP,
4146 Matrix::Translation(intBounds.TopLeft()));
4147 if (DrawRectAccel(Rect(intBounds), pattern, aOptions,
4148 useBitmaps ? Nothing() : Some(color), &handle, false,
4149 true, true)) {
4150 return true;
4152 } else {
4153 handle = nullptr;
4155 // If we get here, either there wasn't a cached texture handle or it
4156 // wasn't valid. Render the text run into a temporary target.
4157 RefPtr<DrawTargetSkia> textDT = new DrawTargetSkia;
4158 if (textDT->Init(intBounds.Size(),
4159 lightOnDark && !useBitmaps && !aUseSubpixelAA
4160 ? SurfaceFormat::A8
4161 : SurfaceFormat::B8G8R8A8)) {
4162 if (!lightOnDark) {
4163 // If rendering dark-on-light text, we need to clear the background to
4164 // white while using an opaque alpha value to allow this.
4165 textDT->FillRect(Rect(IntRect(IntPoint(), intBounds.Size())),
4166 ColorPattern(DeviceColor(1, 1, 1, 1)),
4167 DrawOptions(1.0f, CompositionOp::OP_OVER));
4169 textDT->SetTransform(currentTransform *
4170 Matrix::Translation(-intBounds.TopLeft()));
4171 textDT->SetPermitSubpixelAA(aUseSubpixelAA);
4172 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER,
4173 aOptions.mAntialiasMode);
4174 // If bitmaps might be used, then we have to supply the color, as color
4175 // emoji may ignore it while grayscale bitmaps may use it, with no way to
4176 // know ahead of time. Otherwise, assume the output will be a mask and
4177 // just render it white to determine intensity. Depending on whether the
4178 // text is light or dark, we render white or black text respectively.
4179 ColorPattern colorPattern(
4180 useBitmaps ? color : DeviceColor::Mask(lightOnDark ? 1 : 0, 1));
4181 if (aStrokeOptions) {
4182 textDT->StrokeGlyphs(aFont, aBuffer, colorPattern, *aStrokeOptions,
4183 drawOptions);
4184 } else {
4185 textDT->FillGlyphs(aFont, aBuffer, colorPattern, drawOptions);
4187 if (!lightOnDark) {
4188 uint8_t* data = nullptr;
4189 IntSize size;
4190 int32_t stride = 0;
4191 SurfaceFormat format = SurfaceFormat::UNKNOWN;
4192 if (!textDT->LockBits(&data, &size, &stride, &format)) {
4193 return false;
4195 uint8_t* row = data;
4196 for (int y = 0; y < size.height; ++y) {
4197 uint8_t* px = row;
4198 for (int x = 0; x < size.width; ++x) {
4199 // If rendering dark-on-light text, we need to invert the final mask
4200 // so that it is in the expected white text on transparent black
4201 // format. The alpha will be initialized to the largest of the
4202 // values.
4203 px[0] = 255 - px[0];
4204 px[1] = 255 - px[1];
4205 px[2] = 255 - px[2];
4206 px[3] = std::max(px[0], std::max(px[1], px[2]));
4207 px += 4;
4209 row += stride;
4211 textDT->ReleaseBits(data);
4213 RefPtr<SourceSurface> textSurface = textDT->Snapshot();
4214 if (textSurface) {
4215 // If we don't expect the text surface to contain color glyphs
4216 // such as from subpixel AA, then do one final check to see if
4217 // any ended up in the result. If not, extract the alpha values
4218 // from the surface so we can render it as a mask.
4219 if (textSurface->GetFormat() != SurfaceFormat::A8 &&
4220 !CheckForColorGlyphs(textSurface)) {
4221 textSurface = ExtractAlpha(textSurface, !useBitmaps);
4222 if (!textSurface) {
4223 // Failed extracting alpha for the text surface...
4224 return false;
4227 // Attempt to upload the rendered text surface into a texture
4228 // handle and draw it.
4229 SurfacePattern pattern(textSurface, ExtendMode::CLAMP,
4230 Matrix::Translation(intBounds.TopLeft()));
4231 if (DrawRectAccel(Rect(intBounds), pattern, aOptions,
4232 useBitmaps ? Nothing() : Some(color), &handle, false,
4233 true) &&
4234 handle) {
4235 // If drawing succeeded, then the text surface was uploaded to
4236 // a texture handle. Assign it to the glyph cache entry.
4237 entry->Link(handle);
4238 } else {
4239 // If drawing failed, remove the entry from the cache.
4240 entry->Unlink();
4242 return true;
4246 return false;
4249 void DrawTargetWebgl::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
4250 const Pattern& aPattern,
4251 const DrawOptions& aOptions) {
4252 if (!aFont || !aBuffer.mNumGlyphs) {
4253 return;
4256 bool useSubpixelAA = ShouldUseSubpixelAA(aFont, aOptions);
4258 if (mWebglValid && SupportsDrawOptions(aOptions) &&
4259 aPattern.GetType() == PatternType::COLOR && PrepareContext() &&
4260 mSharedContext->DrawGlyphsAccel(aFont, aBuffer, aPattern, aOptions,
4261 nullptr, useSubpixelAA)) {
4262 return;
4265 // If not able to cache the text run to a texture, then just fall back to
4266 // drawing with the Skia target.
4267 if (useSubpixelAA) {
4268 // Subpixel AA does not support layering because the subpixel masks can't
4269 // blend with the over op.
4270 MarkSkiaChanged();
4271 } else {
4272 MarkSkiaChanged(aOptions);
4274 mSkia->FillGlyphs(aFont, aBuffer, aPattern, aOptions);
4277 void DrawTargetWebgl::SharedContext::WaitForShmem(DrawTargetWebgl* aTarget) {
4278 if (mWaitForShmem) {
4279 // GetError is a sync IPDL call that forces all dispatched commands to be
4280 // flushed. Once it returns, we are certain that any commands processing
4281 // the Shmem have finished.
4282 (void)mWebgl->GetError();
4283 mWaitForShmem = false;
4284 // The sync IPDL call can cause expensive round-trips to add up over time,
4285 // so account for that here.
4286 if (aTarget) {
4287 aTarget->mProfile.OnReadback();
4292 void DrawTargetWebgl::MarkSkiaChanged(const DrawOptions& aOptions) {
4293 if (SupportsLayering(aOptions)) {
4294 WaitForShmem();
4295 if (!mSkiaValid) {
4296 // If the Skia context needs initialization, clear it and enable layering.
4297 mSkiaValid = true;
4298 if (mWebglValid) {
4299 mProfile.OnLayer();
4300 mSkiaLayer = true;
4301 mSkiaLayerClear = mIsClear;
4302 mSkia->DetachAllSnapshots();
4303 if (mSkiaLayerClear) {
4304 // Avoid blending later by making sure the layer background is filled
4305 // with opaque alpha values if necessary.
4306 mSkiaNoClip->FillRect(Rect(mSkiaNoClip->GetRect()), GetClearPattern(),
4307 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
4308 } else {
4309 mSkiaNoClip->ClearRect(Rect(mSkiaNoClip->GetRect()));
4313 // The WebGL context is no longer up-to-date.
4314 mWebglValid = false;
4315 mIsClear = false;
4316 } else {
4317 // For other composition ops, just overwrite the Skia data.
4318 MarkSkiaChanged();
4322 // Attempts to read the contents of the WebGL context into the Skia target.
4323 void DrawTargetWebgl::ReadIntoSkia() {
4324 if (mSkiaValid) {
4325 return;
4327 if (mWebglValid) {
4328 uint8_t* data = nullptr;
4329 IntSize size;
4330 int32_t stride;
4331 SurfaceFormat format;
4332 if (mIsClear) {
4333 // If the WebGL target is still clear, then just clear the Skia target.
4334 mSkia->DetachAllSnapshots();
4335 mSkiaNoClip->FillRect(Rect(mSkiaNoClip->GetRect()), GetClearPattern(),
4336 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
4337 } else {
4338 // If there's no existing snapshot and we can successfully map the Skia
4339 // target for reading, then try to read into that.
4340 if (!mSnapshot && mSkia->LockBits(&data, &size, &stride, &format)) {
4341 (void)ReadInto(data, stride);
4342 mSkia->ReleaseBits(data);
4343 } else if (RefPtr<SourceSurface> snapshot = Snapshot()) {
4344 // Otherwise, fall back to getting a snapshot from WebGL if available
4345 // and then copying that to Skia.
4346 mSkia->CopySurface(snapshot, GetRect(), IntPoint(0, 0));
4348 // Signal that we've hit a complete software fallback.
4349 mProfile.OnFallback();
4352 mSkiaValid = true;
4353 // The Skia data is flat after reading, so disable any layering.
4354 mSkiaLayer = false;
4357 // Reads data from the WebGL context and blends it with the current Skia layer.
4358 void DrawTargetWebgl::FlattenSkia() {
4359 if (!mSkiaValid || !mSkiaLayer) {
4360 return;
4362 mSkiaLayer = false;
4363 if (mSkiaLayerClear) {
4364 // If the WebGL target is clear, then there is nothing to blend.
4365 return;
4367 if (RefPtr<DataSourceSurface> base = ReadSnapshot()) {
4368 mSkia->DetachAllSnapshots();
4369 mSkiaNoClip->DrawSurface(base, Rect(GetRect()), Rect(GetRect()),
4370 DrawSurfaceOptions(SamplingFilter::POINT),
4371 DrawOptions(1.f, CompositionOp::OP_DEST_OVER));
4375 // Attempts to draw the contents of the Skia target into the WebGL context.
4376 bool DrawTargetWebgl::FlushFromSkia() {
4377 // If the WebGL context has been lost, then mark it as invalid and fail.
4378 if (mSharedContext->IsContextLost()) {
4379 mWebglValid = false;
4380 return false;
4382 // The WebGL target is already valid, so there is nothing to do.
4383 if (mWebglValid) {
4384 return true;
4386 // Ensure that DrawRect doesn't recursively call into FlushFromSkia. If
4387 // the Skia target isn't valid, then it doesn't matter what is in the the
4388 // WebGL target either, so only try to blend if there is a valid Skia target.
4389 mWebglValid = true;
4390 if (mSkiaValid) {
4391 AutoRestoreContext restore(this);
4393 // If the Skia target is clear, then there is no need to use a snapshot.
4394 // Directly clear the WebGL target instead.
4395 if (mIsClear) {
4396 if (!DrawRect(Rect(GetRect()), GetClearPattern(),
4397 DrawOptions(1.0f, CompositionOp::OP_SOURCE), Nothing(),
4398 nullptr, false, false, true)) {
4399 mWebglValid = false;
4400 return false;
4402 return true;
4405 RefPtr<SourceSurface> skiaSnapshot = mSkia->Snapshot();
4406 if (!skiaSnapshot) {
4407 // There's a valid Skia target to draw to, but for some reason there is
4408 // no available snapshot, so just keep using the Skia target.
4409 mWebglValid = false;
4410 return false;
4413 // If there is no layer, then just upload it directly.
4414 if (!mSkiaLayer) {
4415 if (PrepareContext(false) && MarkChanged()) {
4416 if (RefPtr<DataSourceSurface> data = skiaSnapshot->GetDataSurface()) {
4417 mSharedContext->UploadSurface(data, mFormat, GetRect(), IntPoint(),
4418 false, false, mTex);
4419 return true;
4422 // Failed to upload the Skia snapshot.
4423 mWebglValid = false;
4424 return false;
4427 SurfacePattern pattern(skiaSnapshot, ExtendMode::CLAMP);
4428 // If there is a layer, blend the snapshot with the WebGL context.
4429 if (!DrawRect(Rect(GetRect()), pattern,
4430 DrawOptions(1.0f, CompositionOp::OP_OVER), Nothing(),
4431 &mSnapshotTexture, false, false, true, true)) {
4432 // If accelerated drawing failed for some reason, then leave the Skia
4433 // target unchanged.
4434 mWebglValid = false;
4435 return false;
4438 return true;
4441 void DrawTargetWebgl::UsageProfile::BeginFrame() {
4442 // Reset the usage profile counters for the new frame.
4443 mFallbacks = 0;
4444 mLayers = 0;
4445 mCacheMisses = 0;
4446 mCacheHits = 0;
4447 mUncachedDraws = 0;
4448 mReadbacks = 0;
4451 void DrawTargetWebgl::UsageProfile::EndFrame() {
4452 bool failed = false;
4453 // If we hit a complete fallback to software rendering, or if cache misses
4454 // were more than cutoff ratio of all requests, then we consider the frame as
4455 // having failed performance profiling.
4456 float cacheRatio =
4457 StaticPrefs::gfx_canvas_accelerated_profile_cache_miss_ratio();
4458 if (mFallbacks > 0 ||
4459 float(mCacheMisses + mReadbacks + mLayers) >
4460 cacheRatio * float(mCacheMisses + mCacheHits + mUncachedDraws +
4461 mReadbacks + mLayers)) {
4462 failed = true;
4464 if (failed) {
4465 ++mFailedFrames;
4467 ++mFrameCount;
4470 bool DrawTargetWebgl::UsageProfile::RequiresRefresh() const {
4471 // If we've rendered at least the required number of frames for a profile and
4472 // more than the cutoff ratio of frames did not meet performance criteria,
4473 // then we should stop using an accelerated canvas.
4474 uint32_t profileFrames = StaticPrefs::gfx_canvas_accelerated_profile_frames();
4475 if (!profileFrames || mFrameCount < profileFrames) {
4476 return false;
4478 float failRatio =
4479 StaticPrefs::gfx_canvas_accelerated_profile_fallback_ratio();
4480 return mFailedFrames > failRatio * mFrameCount;
4483 void DrawTargetWebgl::SharedContext::CachePrefs() {
4484 uint32_t capacity = StaticPrefs::gfx_canvas_accelerated_gpu_path_size() << 20;
4485 if (capacity != mPathVertexCapacity) {
4486 mPathVertexCapacity = capacity;
4487 if (mPathCache) {
4488 mPathCache->ClearVertexRanges();
4490 if (mPathVertexBuffer) {
4491 ResetPathVertexBuffer();
4495 mPathMaxComplexity =
4496 StaticPrefs::gfx_canvas_accelerated_gpu_path_complexity();
4498 mPathAAStroke = StaticPrefs::gfx_canvas_accelerated_aa_stroke_enabled();
4499 mPathWGRStroke = StaticPrefs::gfx_canvas_accelerated_stroke_to_fill_path();
4502 // For use within CanvasRenderingContext2D, called on BorrowDrawTarget.
4503 void DrawTargetWebgl::BeginFrame(const IntRect& aPersistedRect) {
4504 if (mNeedsPresent) {
4505 mNeedsPresent = false;
4506 // If still rendering into the Skia target, switch back to the WebGL
4507 // context.
4508 if (!mWebglValid) {
4509 if (aPersistedRect.IsEmpty()) {
4510 // If nothing needs to persist, just mark the WebGL context valid.
4511 mWebglValid = true;
4512 // Even if the Skia framebuffer is marked clear, since the WebGL
4513 // context is not valid, its contents may be out-of-date and not
4514 // necessarily clear.
4515 mIsClear = false;
4516 } else {
4517 FlushFromSkia();
4521 // Check if we need to clear out any cached because of memory pressure.
4522 mSharedContext->ClearCachesIfNecessary();
4523 // Cache any prefs for the frame.
4524 mSharedContext->CachePrefs();
4525 mProfile.BeginFrame();
4528 // For use within CanvasRenderingContext2D, called on ReturnDrawTarget.
4529 void DrawTargetWebgl::EndFrame() {
4530 if (StaticPrefs::gfx_canvas_accelerated_debug()) {
4531 // Draw a green rectangle in the upper right corner to indicate
4532 // acceleration.
4533 IntRect corner = IntRect(mSize.width - 16, 0, 16, 16).Intersect(GetRect());
4534 DrawRect(Rect(corner), ColorPattern(DeviceColor(0.0f, 1.0f, 0.0f, 1.0f)),
4535 DrawOptions(), Nothing(), nullptr, false, false);
4537 mProfile.EndFrame();
4538 // Ensure we're not somehow using more than the allowed texture memory.
4539 mSharedContext->PruneTextureMemory();
4540 // Signal that we're done rendering the frame in case no present occurs.
4541 mSharedContext->mWebgl->EndOfFrame();
4542 // Check if we need to clear out any cached because of memory pressure.
4543 mSharedContext->ClearCachesIfNecessary();
4544 // The framebuffer is dirty, so it needs to be copied to the swapchain.
4545 mNeedsPresent = true;
4548 Maybe<layers::SurfaceDescriptor> DrawTargetWebgl::GetFrontBuffer() {
4549 // Only try to present and retrieve the front buffer if there is a valid
4550 // WebGL framebuffer that can be sent to the compositor. Otherwise, return
4551 // nothing to try to reuse the Skia snapshot.
4552 if (mNeedsPresent) {
4553 mNeedsPresent = false;
4554 if (mWebglValid || FlushFromSkia()) {
4555 // Copy and swizzle the WebGL framebuffer to the swap chain front buffer.
4556 webgl::SwapChainOptions options;
4557 options.bgra = true;
4558 // Allow async present to be toggled on for accelerated Canvas2D
4559 // independent of WebGL via pref.
4560 options.forceAsyncPresent =
4561 StaticPrefs::gfx_canvas_accelerated_async_present();
4562 mSharedContext->mWebgl->CopyToSwapChain(mFramebuffer, options);
4565 if (mWebglValid) {
4566 return mSharedContext->mWebgl->GetFrontBuffer(mFramebuffer);
4568 return Nothing();
4571 already_AddRefed<DrawTarget> DrawTargetWebgl::CreateSimilarDrawTarget(
4572 const IntSize& aSize, SurfaceFormat aFormat) const {
4573 return mSkia->CreateSimilarDrawTarget(aSize, aFormat);
4576 bool DrawTargetWebgl::CanCreateSimilarDrawTarget(const IntSize& aSize,
4577 SurfaceFormat aFormat) const {
4578 return mSkia->CanCreateSimilarDrawTarget(aSize, aFormat);
4581 RefPtr<DrawTarget> DrawTargetWebgl::CreateClippedDrawTarget(
4582 const Rect& aBounds, SurfaceFormat aFormat) {
4583 return mSkia->CreateClippedDrawTarget(aBounds, aFormat);
4586 already_AddRefed<SourceSurface> DrawTargetWebgl::CreateSourceSurfaceFromData(
4587 unsigned char* aData, const IntSize& aSize, int32_t aStride,
4588 SurfaceFormat aFormat) const {
4589 return mSkia->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat);
4592 already_AddRefed<SourceSurface>
4593 DrawTargetWebgl::CreateSourceSurfaceFromNativeSurface(
4594 const NativeSurface& aSurface) const {
4595 return mSkia->CreateSourceSurfaceFromNativeSurface(aSurface);
4598 already_AddRefed<SourceSurface> DrawTargetWebgl::OptimizeSourceSurface(
4599 SourceSurface* aSurface) const {
4600 if (aSurface->GetType() == SurfaceType::WEBGL) {
4601 return do_AddRef(aSurface);
4603 return mSkia->OptimizeSourceSurface(aSurface);
4606 already_AddRefed<SourceSurface>
4607 DrawTargetWebgl::OptimizeSourceSurfaceForUnknownAlpha(
4608 SourceSurface* aSurface) const {
4609 return mSkia->OptimizeSourceSurfaceForUnknownAlpha(aSurface);
4612 already_AddRefed<GradientStops> DrawTargetWebgl::CreateGradientStops(
4613 GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const {
4614 return mSkia->CreateGradientStops(aStops, aNumStops, aExtendMode);
4617 already_AddRefed<FilterNode> DrawTargetWebgl::CreateFilter(FilterType aType) {
4618 return mSkia->CreateFilter(aType);
4621 void DrawTargetWebgl::DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
4622 const Point& aDestPoint,
4623 const DrawOptions& aOptions) {
4624 MarkSkiaChanged(aOptions);
4625 mSkia->DrawFilter(aNode, aSourceRect, aDestPoint, aOptions);
4628 bool DrawTargetWebgl::Draw3DTransformedSurface(SourceSurface* aSurface,
4629 const Matrix4x4& aMatrix) {
4630 MarkSkiaChanged();
4631 return mSkia->Draw3DTransformedSurface(aSurface, aMatrix);
4634 void DrawTargetWebgl::PushLayer(bool aOpaque, Float aOpacity,
4635 SourceSurface* aMask,
4636 const Matrix& aMaskTransform,
4637 const IntRect& aBounds, bool aCopyBackground) {
4638 PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds,
4639 aCopyBackground, CompositionOp::OP_OVER);
4642 void DrawTargetWebgl::PushLayerWithBlend(bool aOpaque, Float aOpacity,
4643 SourceSurface* aMask,
4644 const Matrix& aMaskTransform,
4645 const IntRect& aBounds,
4646 bool aCopyBackground,
4647 CompositionOp aCompositionOp) {
4648 MarkSkiaChanged(DrawOptions(aOpacity, aCompositionOp));
4649 mSkia->PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds,
4650 aCopyBackground, aCompositionOp);
4651 ++mLayerDepth;
4654 void DrawTargetWebgl::PopLayer() {
4655 MOZ_ASSERT(mSkiaValid);
4656 MOZ_ASSERT(mLayerDepth > 0);
4657 --mLayerDepth;
4658 mSkia->PopLayer();
4661 } // namespace mozilla::gfx