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