DrawTargetWebgl.cpp (266796B)
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/. */ 6 7 #include "DrawTargetWebglInternal.h" 8 #include "FilterNodeWebgl.h" 9 #include "GLContext.h" 10 #include "GLScreenBuffer.h" 11 #include "SharedSurface.h" 12 #include "SourceSurfaceWebgl.h" 13 #include "WebGL2Context.h" 14 #include "WebGLBuffer.h" 15 #include "WebGLChild.h" 16 #include "WebGLContext.h" 17 #include "WebGLFramebuffer.h" 18 #include "WebGLProgram.h" 19 #include "WebGLShader.h" 20 #include "WebGLTexture.h" 21 #include "WebGLVertexArray.h" 22 #include "gfxPlatform.h" 23 #include "mozilla/ClearOnShutdown.h" 24 #include "mozilla/HelperMacros.h" 25 #include "mozilla/StaticPrefs_gfx.h" 26 #include "mozilla/gfx/AAStroke.h" 27 #include "mozilla/gfx/Blur.h" 28 #include "mozilla/gfx/DataSurfaceHelpers.h" 29 #include "mozilla/gfx/DrawTargetSkia.h" 30 #include "mozilla/gfx/Helpers.h" 31 #include "mozilla/gfx/HelpersSkia.h" 32 #include "mozilla/gfx/Logging.h" 33 #include "mozilla/gfx/PathHelpers.h" 34 #include "mozilla/gfx/PathSkia.h" 35 #include "mozilla/gfx/Scale.h" 36 #include "mozilla/gfx/Swizzle.h" 37 #include "mozilla/gfx/gfxVars.h" 38 #include "mozilla/layers/ImageDataSerializer.h" 39 #include "mozilla/layers/RemoteTextureMap.h" 40 #include "mozilla/widget/ScreenManager.h" 41 #include "nsContentUtils.h" 42 #include "nsIMemoryReporter.h" 43 #include "skia/include/core/SkPixmap.h" 44 45 #ifdef XP_MACOSX 46 # include "mozilla/gfx/ScaledFontMac.h" 47 #endif 48 49 namespace mozilla::gfx { 50 51 static Atomic<size_t> gReportedTextureMemory; 52 static Atomic<size_t> gReportedHeapData; 53 static Atomic<size_t> gReportedContextCount; 54 static Atomic<size_t> gReportedTargetCount; 55 56 class AcceleratedCanvas2DMemoryReporter final : public nsIMemoryReporter { 57 ~AcceleratedCanvas2DMemoryReporter() = default; 58 59 public: 60 NS_DECL_THREADSAFE_ISUPPORTS 61 62 MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(MallocSizeOfOnAlloc) 63 MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(MallocSizeOfOnFree) 64 65 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, 66 nsISupports* aData, bool aAnonymize) override { 67 MOZ_COLLECT_REPORT("ac2d-texture-memory", KIND_OTHER, UNITS_BYTES, 68 gReportedTextureMemory, 69 "GPU memory used by Accelerated Canvas2D textures."); 70 MOZ_COLLECT_REPORT("explicit/ac2d/heap-resources", KIND_HEAP, UNITS_BYTES, 71 gReportedHeapData, 72 "Heap overhead for Accelerated Canvas2D resources."); 73 MOZ_COLLECT_REPORT("ac2d-context-count", KIND_OTHER, UNITS_COUNT, 74 gReportedContextCount, 75 "Number of Accelerated Canvas2D contexts."); 76 MOZ_COLLECT_REPORT("ac2d-target-count", KIND_OTHER, UNITS_COUNT, 77 gReportedTargetCount, 78 "Number of Accelerated Canvas2D targets."); 79 return NS_OK; 80 } 81 82 static void Register() { 83 static bool registered = false; 84 if (!registered) { 85 registered = true; 86 RegisterStrongMemoryReporter(new AcceleratedCanvas2DMemoryReporter); 87 } 88 } 89 }; 90 91 NS_IMPL_ISUPPORTS(AcceleratedCanvas2DMemoryReporter, nsIMemoryReporter) 92 93 BackingTexture::BackingTexture(const IntSize& aSize, SurfaceFormat aFormat, 94 const RefPtr<WebGLTexture>& aTexture) 95 : mSize(aSize), mFormat(aFormat), mTexture(aTexture) {} 96 97 #ifdef XP_WIN 98 // Work around buggy ANGLE/D3D drivers that may copy blocks of pixels outside 99 // the row length. Extra space is reserved at the end of each row up to stride 100 // alignment. This does not affect standalone textures. 101 static const Etagere::AllocatorOptions kR8AllocatorOptions = {16, 1, 1, 0}; 102 #endif 103 104 SharedTexture::SharedTexture(const IntSize& aSize, SurfaceFormat aFormat, 105 const RefPtr<WebGLTexture>& aTexture) 106 : BackingTexture(aSize, aFormat, aTexture), 107 mAtlasAllocator( 108 #ifdef XP_WIN 109 aFormat == SurfaceFormat::A8 110 ? Etagere::etagere_atlas_allocator_with_options( 111 aSize.width, aSize.height, &kR8AllocatorOptions) 112 : 113 #endif 114 Etagere::etagere_atlas_allocator_new(aSize.width, aSize.height)) { 115 } 116 117 SharedTexture::~SharedTexture() { 118 if (mAtlasAllocator) { 119 Etagere::etagere_atlas_allocator_delete(mAtlasAllocator); 120 mAtlasAllocator = nullptr; 121 } 122 } 123 124 SharedTextureHandle::SharedTextureHandle(Etagere::AllocationId aId, 125 const IntRect& aBounds, 126 SharedTexture* aTexture) 127 : mAllocationId(aId), mBounds(aBounds), mTexture(aTexture) {} 128 129 already_AddRefed<SharedTextureHandle> SharedTexture::Allocate( 130 const IntSize& aSize) { 131 Etagere::Allocation alloc = {{0, 0, 0, 0}, Etagere::INVALID_ALLOCATION_ID}; 132 if (!mAtlasAllocator || 133 !Etagere::etagere_atlas_allocator_allocate(mAtlasAllocator, aSize.width, 134 aSize.height, &alloc) || 135 alloc.id == Etagere::INVALID_ALLOCATION_ID) { 136 return nullptr; 137 } 138 RefPtr<SharedTextureHandle> handle = new SharedTextureHandle( 139 alloc.id, 140 IntRect(IntPoint(alloc.rectangle.min_x, alloc.rectangle.min_y), aSize), 141 this); 142 return handle.forget(); 143 } 144 145 bool SharedTexture::Free(SharedTextureHandle& aHandle) { 146 if (aHandle.mTexture != this) { 147 return false; 148 } 149 if (aHandle.mAllocationId != Etagere::INVALID_ALLOCATION_ID) { 150 if (mAtlasAllocator) { 151 Etagere::etagere_atlas_allocator_deallocate(mAtlasAllocator, 152 aHandle.mAllocationId); 153 } 154 aHandle.mAllocationId = Etagere::INVALID_ALLOCATION_ID; 155 } 156 return true; 157 } 158 159 StandaloneTexture::StandaloneTexture(const IntSize& aSize, 160 SurfaceFormat aFormat, 161 const RefPtr<WebGLTexture>& aTexture) 162 : BackingTexture(aSize, aFormat, aTexture) {} 163 164 DrawTargetWebgl::DrawTargetWebgl() = default; 165 166 inline void SharedContextWebgl::ClearLastTexture(bool aFullClear) { 167 mLastTexture = nullptr; 168 if (aFullClear) { 169 mLastClipMask = nullptr; 170 } 171 } 172 173 // Attempts to clear the snapshot state. If the snapshot is only referenced by 174 // this target, then it should simply be destroyed. If it is a WebGL surface in 175 // use by something else, then special cleanup such as reusing the texture or 176 // copy-on-write may be possible. 177 void DrawTargetWebgl::ClearSnapshot(bool aCopyOnWrite, bool aNeedHandle) { 178 if (!mSnapshot) { 179 return; 180 } 181 mSharedContext->ClearLastTexture(); 182 RefPtr<SourceSurfaceWebgl> snapshot = mSnapshot.forget(); 183 if (snapshot->hasOneRef()) { 184 return; 185 } 186 if (aCopyOnWrite) { 187 // WebGL snapshots must be notified that the framebuffer contents will be 188 // changing so that it can copy the data. 189 snapshot->DrawTargetWillChange(aNeedHandle); 190 } else { 191 // If not copying, then give the backing texture to the surface for reuse. 192 snapshot->GiveTexture( 193 mSharedContext->WrapSnapshot(GetSize(), GetFormat(), mTex.forget())); 194 } 195 } 196 197 DrawTargetWebgl::~DrawTargetWebgl() { 198 ClearSnapshot(false); 199 if (mSharedContext) { 200 // Force any Skia snapshots to copy the shmem before it deallocs. 201 if (mSkia) { 202 mSkia->DetachAllSnapshots(); 203 } 204 mSharedContext->ClearLastTexture(true); 205 if (mClipMask) { 206 mSharedContext->RemoveUntrackedTextureMemory(mClipMask); 207 mClipMask = nullptr; 208 } 209 mFramebuffer = nullptr; 210 if (mTex) { 211 mSharedContext->RemoveUntrackedTextureMemory(mTex); 212 mTex = nullptr; 213 } 214 mSharedContext->mDrawTargetCount--; 215 gReportedTargetCount--; 216 } 217 } 218 219 SharedContextWebgl::SharedContextWebgl() = default; 220 221 SharedContextWebgl::~SharedContextWebgl() { 222 // Detect context loss before deletion. 223 if (mWebgl) { 224 ExitTlsScope(); 225 mWebgl->ActiveTexture(0); 226 gReportedContextCount--; 227 } 228 if (mWGRPathBuilder) { 229 WGR::wgr_builder_release(mWGRPathBuilder); 230 mWGRPathBuilder = nullptr; 231 } 232 if (mWGROutputBuffer) { 233 RemoveHeapData(mWGROutputBuffer.get()); 234 mWGROutputBuffer = nullptr; 235 } 236 if (mPathVertexBuffer) { 237 RemoveUntrackedTextureMemory(mPathVertexBuffer); 238 mPathVertexBuffer = nullptr; 239 } 240 ClearZeroBuffer(); 241 ClearAllTextures(); 242 UnlinkSurfaceTextures(true); 243 UnlinkGlyphCaches(); 244 ClearSnapshotPBOs(); 245 } 246 247 gl::GLContext* SharedContextWebgl::GetGLContext() { 248 return mWebgl ? mWebgl->GL() : nullptr; 249 } 250 251 void SharedContextWebgl::EnterTlsScope() { 252 if (mTlsScope.isSome()) { 253 return; 254 } 255 if (gl::GLContext* gl = GetGLContext()) { 256 mTlsScope = Some(gl->mUseTLSIsCurrent); 257 gl::GLContext::InvalidateCurrentContext(); 258 gl->mUseTLSIsCurrent = true; 259 } 260 } 261 262 void SharedContextWebgl::ExitTlsScope() { 263 if (mTlsScope.isNothing()) { 264 return; 265 } 266 if (gl::GLContext* gl = GetGLContext()) { 267 gl->mUseTLSIsCurrent = mTlsScope.value(); 268 } 269 mTlsScope = Nothing(); 270 } 271 272 // Remove any SourceSurface user data associated with this TextureHandle. 273 inline void SharedContextWebgl::UnlinkSurfaceTexture( 274 const RefPtr<TextureHandle>& aHandle, bool aForce) { 275 if (RefPtr<SourceSurface> surface = aHandle->GetSurface()) { 276 // Ensure any WebGL snapshot textures get unlinked. 277 if (surface->GetType() == SurfaceType::WEBGL) { 278 static_cast<SourceSurfaceWebgl*>(surface.get()) 279 ->OnUnlinkTexture(this, aHandle, aForce); 280 } 281 surface->RemoveUserData(&mTextureHandleKey); 282 } 283 } 284 285 // Unlinks TextureHandles from any SourceSurface user data. 286 void SharedContextWebgl::UnlinkSurfaceTextures(bool aForce) { 287 for (RefPtr<TextureHandle> handle = mTextureHandles.getFirst(); handle; 288 handle = handle->getNext()) { 289 UnlinkSurfaceTexture(handle, aForce); 290 } 291 } 292 293 // Unlinks GlyphCaches from any ScaledFont user data. 294 void SharedContextWebgl::UnlinkGlyphCaches() { 295 GlyphCache* cache = mGlyphCaches.getFirst(); 296 while (cache) { 297 ScaledFont* font = cache->GetFont(); 298 // Access the next cache before removing the user data, as it might destroy 299 // the cache. 300 cache = cache->getNext(); 301 font->RemoveUserData(&mGlyphCacheKey); 302 } 303 } 304 305 void SharedContextWebgl::OnMemoryPressure() { mShouldClearCaches = true; } 306 307 void SharedContextWebgl::ClearCaches() { 308 OnMemoryPressure(); 309 ClearCachesIfNecessary(); 310 } 311 312 // Clear out the entire list of texture handles from any source. 313 void SharedContextWebgl::ClearAllTextures() { 314 while (!mTextureHandles.isEmpty()) { 315 PruneTextureHandle(mTextureHandles.popLast()); 316 --mNumTextureHandles; 317 } 318 } 319 320 static inline size_t TextureMemoryUsage(WebGLTexture* aTexture) { 321 return aTexture->MemoryUsage(); 322 } 323 324 static inline size_t TextureMemoryUsage(WebGLBuffer* aBuffer) { 325 return aBuffer->ByteLength(); 326 } 327 328 inline void SharedContextWebgl::AddHeapData(const void* aBuf) { 329 if (aBuf) { 330 gReportedHeapData += 331 AcceleratedCanvas2DMemoryReporter::MallocSizeOfOnAlloc(aBuf); 332 } 333 } 334 335 inline void SharedContextWebgl::RemoveHeapData(const void* aBuf) { 336 if (aBuf) { 337 gReportedHeapData -= 338 AcceleratedCanvas2DMemoryReporter::MallocSizeOfOnFree(aBuf); 339 } 340 } 341 342 inline void SharedContextWebgl::AddUntrackedTextureMemory(size_t aBytes) { 343 gReportedTextureMemory += aBytes; 344 } 345 346 inline void SharedContextWebgl::RemoveUntrackedTextureMemory(size_t aBytes) { 347 gReportedTextureMemory -= aBytes; 348 } 349 350 template <typename T> 351 inline void SharedContextWebgl::AddUntrackedTextureMemory( 352 const RefPtr<T>& aObject, size_t aBytes) { 353 size_t usedBytes = aBytes > 0 ? aBytes : TextureMemoryUsage(aObject); 354 AddUntrackedTextureMemory(usedBytes); 355 gReportedHeapData += aObject->SizeOfIncludingThis( 356 AcceleratedCanvas2DMemoryReporter::MallocSizeOfOnAlloc); 357 } 358 359 template <typename T> 360 inline void SharedContextWebgl::RemoveUntrackedTextureMemory( 361 const RefPtr<T>& aObject, size_t aBytes) { 362 size_t usedBytes = aBytes > 0 ? aBytes : TextureMemoryUsage(aObject); 363 RemoveUntrackedTextureMemory(usedBytes); 364 gReportedHeapData -= aObject->SizeOfIncludingThis( 365 AcceleratedCanvas2DMemoryReporter::MallocSizeOfOnFree); 366 } 367 368 inline void SharedContextWebgl::AddTextureMemory(BackingTexture* aTexture) { 369 size_t usedBytes = aTexture->UsedBytes(); 370 mTotalTextureMemory += usedBytes; 371 AddUntrackedTextureMemory(aTexture->GetWebGLTexture(), usedBytes); 372 } 373 374 inline void SharedContextWebgl::RemoveTextureMemory(BackingTexture* aTexture) { 375 size_t usedBytes = aTexture->UsedBytes(); 376 mTotalTextureMemory -= usedBytes; 377 RemoveUntrackedTextureMemory(aTexture->GetWebGLTexture(), usedBytes); 378 } 379 380 // Scan through the shared texture pages looking for any that are empty and 381 // delete them. 382 void SharedContextWebgl::ClearEmptyTextureMemory() { 383 for (auto pos = mSharedTextures.begin(); pos != mSharedTextures.end();) { 384 if (!(*pos)->HasAllocatedHandles()) { 385 RefPtr<SharedTexture> shared = *pos; 386 mEmptyTextureMemory -= shared->UsedBytes(); 387 RemoveTextureMemory(shared); 388 pos = mSharedTextures.erase(pos); 389 } else { 390 ++pos; 391 } 392 } 393 } 394 395 void SharedContextWebgl::ClearZeroBuffer() { 396 if (mZeroBuffer) { 397 RemoveUntrackedTextureMemory(mZeroBuffer); 398 mZeroBuffer = nullptr; 399 } 400 } 401 402 // If there is a request to clear out the caches because of memory pressure, 403 // then first clear out all the texture handles in the texture cache. If there 404 // are still empty texture pages being kept around, then clear those too. 405 void SharedContextWebgl::ClearCachesIfNecessary() { 406 if (!mShouldClearCaches.exchange(false)) { 407 return; 408 } 409 ClearZeroBuffer(); 410 ClearAllTextures(); 411 if (mEmptyTextureMemory) { 412 ClearEmptyTextureMemory(); 413 } 414 ClearLastTexture(); 415 ClearSnapshotPBOs(); 416 } 417 418 // Try to initialize a new WebGL context. Verifies that the requested size does 419 // not exceed the available texture limits and that shader creation succeeded. 420 bool DrawTargetWebgl::Init(const IntSize& size, const SurfaceFormat format, 421 const RefPtr<SharedContextWebgl>& aSharedContext) { 422 switch (format) { 423 case SurfaceFormat::B8G8R8A8: 424 case SurfaceFormat::B8G8R8X8: 425 break; 426 default: 427 MOZ_ASSERT_UNREACHABLE("Unsupported format for DrawTargetWebgl."); 428 return false; 429 } 430 431 mSize = size; 432 mFormat = format; 433 434 if (!aSharedContext || aSharedContext->IsContextLost() || 435 aSharedContext->mDrawTargetCount >= 436 StaticPrefs::gfx_canvas_accelerated_max_draw_target_count()) { 437 return false; 438 } 439 mSharedContext = aSharedContext; 440 mSharedContext->mDrawTargetCount++; 441 gReportedTargetCount++; 442 443 if (size_t(std::max(size.width, size.height)) > 444 mSharedContext->mMaxTextureSize) { 445 return false; 446 } 447 448 if (!CreateFramebuffer()) { 449 return false; 450 } 451 452 size_t byteSize = layers::ImageDataSerializer::ComputeRGBBufferSize( 453 mSize, SurfaceFormat::B8G8R8A8); 454 if (byteSize == 0) { 455 return false; 456 } 457 458 size_t shmemSize = mozilla::ipc::shared_memory::PageAlignedSize(byteSize); 459 if (NS_WARN_IF(shmemSize > UINT32_MAX)) { 460 MOZ_ASSERT_UNREACHABLE("Buffer too big?"); 461 return false; 462 } 463 464 auto handle = mozilla::ipc::shared_memory::Create(shmemSize); 465 if (NS_WARN_IF(!handle)) { 466 return false; 467 } 468 auto mapping = handle.Map(); 469 if (NS_WARN_IF(!mapping)) { 470 return false; 471 } 472 473 mShmemHandle = std::move(handle).ToReadOnly(); 474 mShmem = std::move(mapping); 475 476 mSkia = new DrawTargetSkia; 477 auto stride = layers::ImageDataSerializer::ComputeRGBStride( 478 SurfaceFormat::B8G8R8A8, size.width); 479 if (!mSkia->Init(mShmem.DataAs<uint8_t>(), size, stride, 480 SurfaceFormat::B8G8R8A8, true)) { 481 return false; 482 } 483 484 // Allocate an unclipped copy of the DT pointing to its data. 485 uint8_t* dtData = nullptr; 486 IntSize dtSize; 487 int32_t dtStride = 0; 488 SurfaceFormat dtFormat = SurfaceFormat::UNKNOWN; 489 if (!mSkia->LockBits(&dtData, &dtSize, &dtStride, &dtFormat)) { 490 return false; 491 } 492 mSkiaNoClip = new DrawTargetSkia; 493 if (!mSkiaNoClip->Init(dtData, dtSize, dtStride, dtFormat, true)) { 494 mSkia->ReleaseBits(dtData); 495 return false; 496 } 497 mSkia->ReleaseBits(dtData); 498 499 SetPermitSubpixelAA(IsOpaque(format)); 500 return true; 501 } 502 503 // If a non-recoverable error occurred that would stop the canvas from initing. 504 static Atomic<bool> sContextInitError(false); 505 506 already_AddRefed<SharedContextWebgl> SharedContextWebgl::Create() { 507 // If context initialization would fail, don't even try to create a context. 508 if (sContextInitError) { 509 return nullptr; 510 } 511 RefPtr<SharedContextWebgl> sharedContext = new SharedContextWebgl; 512 if (!sharedContext->Initialize()) { 513 return nullptr; 514 } 515 return sharedContext.forget(); 516 } 517 518 bool SharedContextWebgl::Initialize() { 519 AcceleratedCanvas2DMemoryReporter::Register(); 520 521 WebGLContextOptions options = {}; 522 options.alpha = true; 523 options.depth = false; 524 options.stencil = false; 525 options.antialias = false; 526 options.preserveDrawingBuffer = true; 527 options.failIfMajorPerformanceCaveat = false; 528 529 const bool resistFingerprinting = nsContentUtils::ShouldResistFingerprinting( 530 "Fallback", RFPTarget::WebGLRenderCapability); 531 const auto initDesc = webgl::InitContextDesc{ 532 .isWebgl2 = true, 533 .resistFingerprinting = resistFingerprinting, 534 .principalKey = 0, 535 .size = {1, 1}, 536 .options = options, 537 }; 538 539 webgl::InitContextResult initResult; 540 mWebgl = WebGLContext::Create(nullptr, initDesc, &initResult); 541 if (!mWebgl) { 542 // There was a non-recoverable error when trying to create a host context. 543 sContextInitError = true; 544 mWebgl = nullptr; 545 return false; 546 } 547 if (mWebgl->IsContextLost()) { 548 mWebgl = nullptr; 549 return false; 550 } 551 552 mMaxTextureSize = initResult.limits.maxTex2dSize; 553 554 if (kIsMacOS) { 555 mRasterizationTruncates = initResult.vendor == gl::GLVendor::ATI; 556 } 557 558 CachePrefs(); 559 560 if (!CreateShaders()) { 561 // There was a non-recoverable error when trying to init shaders. 562 sContextInitError = true; 563 mWebgl = nullptr; 564 return false; 565 } 566 567 mWGRPathBuilder = WGR::wgr_new_builder(); 568 569 gReportedContextCount++; 570 571 return true; 572 } 573 574 inline void SharedContextWebgl::BlendFunc(GLenum aSrcFactor, 575 GLenum aDstFactor) { 576 mWebgl->BlendFuncSeparate({}, aSrcFactor, aDstFactor, aSrcFactor, aDstFactor); 577 } 578 579 void SharedContextWebgl::SetBlendState(CompositionOp aOp, 580 const Maybe<DeviceColor>& aColor, 581 uint8_t aStage) { 582 if (aOp == mLastCompositionOp && mLastBlendColor == aColor && 583 mLastBlendStage == aStage) { 584 return; 585 } 586 mLastCompositionOp = aOp; 587 mLastBlendColor = aColor; 588 mLastBlendStage = aStage; 589 // AA is not supported for all composition ops, so switching blend modes may 590 // cause a toggle in AA state. Certain ops such as OP_SOURCE require output 591 // alpha that is blended separately from AA coverage. This would require two 592 // stage blending which can incur a substantial performance penalty, so to 593 // work around this currently we just disable AA for those ops. 594 595 // Map the composition op to a WebGL blend mode, if possible. 596 bool enabled = true; 597 switch (aOp) { 598 case CompositionOp::OP_OVER: 599 if (aColor) { 600 // If a color is supplied, then we blend subpixel text. 601 mWebgl->BlendColor(aColor->b, aColor->g, aColor->r, 1.0f); 602 BlendFunc(LOCAL_GL_CONSTANT_COLOR, LOCAL_GL_ONE_MINUS_SRC_COLOR); 603 } else { 604 BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA); 605 } 606 break; 607 case CompositionOp::OP_DEST_OVER: 608 BlendFunc(LOCAL_GL_ONE_MINUS_DST_ALPHA, LOCAL_GL_ONE); 609 break; 610 case CompositionOp::OP_ADD: 611 BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE); 612 break; 613 case CompositionOp::OP_DEST_OUT: 614 BlendFunc(LOCAL_GL_ZERO, LOCAL_GL_ONE_MINUS_SRC_ALPHA); 615 break; 616 case CompositionOp::OP_ATOP: 617 BlendFunc(LOCAL_GL_DST_ALPHA, LOCAL_GL_ONE_MINUS_SRC_ALPHA); 618 break; 619 case CompositionOp::OP_SOURCE: 620 if (aColor) { 621 // If a color is supplied, then we assume there is clipping or AA. This 622 // requires that we still use an over blend func with the clip/AA alpha, 623 // while filling the interior with the unaltered color. Normally this 624 // would require dual source blending, but we can emulate it with only 625 // a blend color. 626 mWebgl->BlendColor(aColor->b, aColor->g, aColor->r, aColor->a); 627 BlendFunc(LOCAL_GL_CONSTANT_COLOR, LOCAL_GL_ONE_MINUS_SRC_COLOR); 628 } else { 629 enabled = false; 630 } 631 break; 632 case CompositionOp::OP_CLEAR: 633 // Assume the source is an alpha mask for clearing. Be careful to blend in 634 // the correct alpha if the target is opaque. 635 mWebgl->BlendFuncSeparate( 636 {}, LOCAL_GL_ZERO, LOCAL_GL_ONE_MINUS_SRC_ALPHA, 637 IsOpaque(mCurrentTarget->GetFormat()) ? LOCAL_GL_ONE : LOCAL_GL_ZERO, 638 LOCAL_GL_ONE_MINUS_SRC_ALPHA); 639 break; 640 case CompositionOp::OP_MULTIPLY: 641 switch (aStage) { 642 // Single stage, assume dest is opaque alpha. 643 case 0: 644 BlendFunc(LOCAL_GL_DST_COLOR, LOCAL_GL_ONE_MINUS_SRC_ALPHA); 645 break; 646 // Multi-stage, decompose into [Cs*(1 - Ad)] + [Cd*(1 - As) + Cs*Cd] 647 case 1: 648 BlendFunc(LOCAL_GL_DST_COLOR, LOCAL_GL_ONE_MINUS_SRC_ALPHA); 649 break; 650 case 2: 651 BlendFunc(LOCAL_GL_ONE_MINUS_DST_ALPHA, LOCAL_GL_ONE); 652 break; 653 } 654 break; 655 case CompositionOp::OP_SCREEN: 656 BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_COLOR); 657 break; 658 case CompositionOp::OP_IN: // unbounded 659 BlendFunc(LOCAL_GL_DST_ALPHA, LOCAL_GL_ZERO); 660 break; 661 case CompositionOp::OP_OUT: // unbounded 662 BlendFunc(LOCAL_GL_ONE_MINUS_DST_ALPHA, LOCAL_GL_ZERO); 663 break; 664 case CompositionOp::OP_DEST_IN: // unbounded 665 BlendFunc(LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA); 666 break; 667 case CompositionOp::OP_DEST_ATOP: // unbounded 668 BlendFunc(LOCAL_GL_ONE_MINUS_DST_ALPHA, LOCAL_GL_SRC_ALPHA); 669 break; 670 default: 671 enabled = false; 672 break; 673 } 674 675 mWebgl->SetEnabled(LOCAL_GL_BLEND, {}, enabled); 676 } 677 678 // Ensure the WebGL framebuffer is set to the current target. 679 bool SharedContextWebgl::SetTarget(DrawTargetWebgl* aDT, 680 const RefPtr<TextureHandle>& aHandle, 681 const IntSize& aViewportSize) { 682 if (!mWebgl || mWebgl->IsContextLost()) { 683 return false; 684 } 685 if (aDT != mCurrentTarget || mTargetHandle != aHandle) { 686 mCurrentTarget = aDT; 687 mTargetHandle = aHandle; 688 IntRect bounds; 689 if (aHandle) { 690 if (!mTargetFramebuffer) { 691 mTargetFramebuffer = mWebgl->CreateFramebuffer(); 692 } 693 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mTargetFramebuffer); 694 695 webgl::FbAttachInfo attachInfo; 696 attachInfo.tex = aHandle->GetBackingTexture()->GetWebGLTexture(); 697 mWebgl->FramebufferAttach(LOCAL_GL_FRAMEBUFFER, 698 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D, 699 attachInfo); 700 701 bounds = aHandle->GetBounds(); 702 } else if (aDT) { 703 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, aDT->mFramebuffer); 704 bounds = aDT->GetRect(); 705 } 706 mViewportSize = !aViewportSize.IsEmpty() ? Min(aViewportSize, bounds.Size()) 707 : bounds.Size(); 708 mWebgl->Viewport(bounds.x, bounds.y, mViewportSize.width, 709 mViewportSize.height); 710 } 711 return true; 712 } 713 714 // Replace the current clip rect with a new potentially-AA'd clip rect. 715 void SharedContextWebgl::SetClipRect(const Rect& aClipRect) { 716 // Only invalidate the clip rect if it actually changes. 717 if (!mClipAARect.IsEqualEdges(aClipRect)) { 718 mClipAARect = aClipRect; 719 // Store the integer-aligned bounds. 720 mClipRect = RoundedOut(aClipRect); 721 } 722 } 723 724 bool SharedContextWebgl::SetClipMask(const RefPtr<WebGLTexture>& aTex) { 725 if (mLastClipMask != aTex) { 726 if (!mWebgl) { 727 return false; 728 } 729 mWebgl->ActiveTexture(1); 730 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, aTex); 731 mWebgl->ActiveTexture(0); 732 mLastClipMask = aTex; 733 } 734 return true; 735 } 736 737 bool SharedContextWebgl::SetNoClipMask() { 738 if (mNoClipMask) { 739 return SetClipMask(mNoClipMask); 740 } 741 if (!mWebgl) { 742 return false; 743 } 744 mNoClipMask = mWebgl->CreateTexture(); 745 if (!mNoClipMask) { 746 return false; 747 } 748 mWebgl->ActiveTexture(1); 749 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mNoClipMask); 750 static const auto solidMask = 751 std::array<const uint8_t, 4>{0xFF, 0xFF, 0xFF, 0xFF}; 752 mWebgl->TexImage(0, LOCAL_GL_RGBA8, {0, 0, 0}, 753 {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE}, 754 {LOCAL_GL_TEXTURE_2D, 755 {1, 1, 1}, 756 gfxAlphaType::NonPremult, 757 Some(Span{solidMask})}); 758 InitTexParameters(mNoClipMask, false); 759 mWebgl->ActiveTexture(0); 760 mLastClipMask = mNoClipMask; 761 return true; 762 } 763 764 inline bool DrawTargetWebgl::ClipStack::operator==( 765 const DrawTargetWebgl::ClipStack& aOther) const { 766 // Verify the transform and bounds match. 767 if (!mTransform.FuzzyEquals(aOther.mTransform) || 768 !mRect.IsEqualInterior(aOther.mRect)) { 769 return false; 770 } 771 // Verify the paths match. 772 if (!mPath) { 773 return !aOther.mPath; 774 } 775 if (!aOther.mPath || 776 mPath->GetBackendType() != aOther.mPath->GetBackendType()) { 777 return false; 778 } 779 if (mPath->GetBackendType() != BackendType::SKIA) { 780 return mPath == aOther.mPath; 781 } 782 return static_cast<const PathSkia*>(mPath.get())->GetPath() == 783 static_cast<const PathSkia*>(aOther.mPath.get())->GetPath(); 784 } 785 786 // If the clip region can't be approximated by a simple clip rect, then we need 787 // to generate a clip mask that can represent the clip region per-pixel. We 788 // render to the Skia target temporarily, transparent outside the clip region, 789 // opaque inside, and upload this to a texture that can be used by the shaders. 790 bool DrawTargetWebgl::GenerateComplexClipMask() { 791 if (!mClipChanged || (mClipMask && mCachedClipStack == mClipStack)) { 792 mClipChanged = false; 793 // If the clip mask was already generated, use the cached mask and bounds. 794 mSharedContext->SetClipMask(mClipMask); 795 mSharedContext->SetClipRect(mClipBounds); 796 return true; 797 } 798 if (!mWebglValid) { 799 // If the Skia target is currently being used, then we can't render the mask 800 // in it. 801 return false; 802 } 803 RefPtr<WebGLContext> webgl = mSharedContext->mWebgl; 804 if (!webgl) { 805 return false; 806 } 807 bool init = false; 808 if (!mClipMask) { 809 mClipMask = webgl->CreateTexture(); 810 if (!mClipMask) { 811 return false; 812 } 813 init = true; 814 } 815 // Try to get the bounds of the clip to limit the size of the mask. 816 if (Maybe<IntRect> clip = mSkia->GetDeviceClipRect(true)) { 817 mClipBounds = *clip; 818 } else { 819 // If we can't get bounds, then just use the entire viewport. 820 mClipBounds = GetRect(); 821 } 822 mClipAARect = Rect(mClipBounds); 823 // If initializing the clip mask, then allocate the entire texture to ensure 824 // all pixels get filled with an empty mask regardless. Otherwise, restrict 825 // uploading to only the clip region. 826 RefPtr<DrawTargetSkia> dt = new DrawTargetSkia; 827 if (!dt->Init(mClipBounds.Size(), SurfaceFormat::A8)) { 828 if (init) { 829 // Ensure that the clip mask is reinitialized on the next attempt. 830 mClipMask = nullptr; 831 } 832 return false; 833 } 834 // Set the clip region and fill the entire inside of it 835 // with opaque white. 836 mCachedClipStack.clear(); 837 for (auto& clipStack : mClipStack) { 838 // Record the current state of the clip stack for this mask. 839 mCachedClipStack.push_back(clipStack); 840 dt->SetTransform( 841 Matrix(clipStack.mTransform).PostTranslate(-mClipBounds.TopLeft())); 842 if (clipStack.mPath) { 843 dt->PushClip(clipStack.mPath); 844 } else { 845 dt->PushClipRect(clipStack.mRect); 846 } 847 } 848 dt->SetTransform(Matrix::Translation(-mClipBounds.TopLeft())); 849 dt->FillRect(Rect(mClipBounds), ColorPattern(DeviceColor(1, 1, 1, 1))); 850 // Bind the clip mask for uploading. This is done on texture unit 0 so that 851 // we can work around an Windows Intel driver bug. If done on texture unit 1, 852 // the driver doesn't notice that the texture contents was modified. Force a 853 // re-latch by binding the texture on texture unit 1 only after modification. 854 webgl->BindTexture(LOCAL_GL_TEXTURE_2D, mClipMask); 855 if (init) { 856 mSharedContext->InitTexParameters(mClipMask, false); 857 } 858 RefPtr<DataSourceSurface> data; 859 if (RefPtr<SourceSurface> snapshot = dt->Snapshot()) { 860 data = snapshot->GetDataSurface(); 861 } 862 // Finally, upload the texture data and initialize texture storage if 863 // necessary. 864 if (init && mClipBounds.Size() != mSize) { 865 mSharedContext->UploadSurface(nullptr, SurfaceFormat::A8, GetRect(), 866 IntPoint(), true, true); 867 init = false; 868 } 869 mSharedContext->UploadSurface(data, SurfaceFormat::A8, 870 IntRect(IntPoint(), mClipBounds.Size()), 871 mClipBounds.TopLeft(), init); 872 mSharedContext->ClearLastTexture(); 873 // Bind the new clip mask to the clip sampler on texture unit 1. 874 mSharedContext->SetClipMask(mClipMask); 875 mSharedContext->SetClipRect(mClipBounds); 876 // We uploaded a surface, just as if we missed the texture cache, so account 877 // for that here. 878 if (init) { 879 mSharedContext->AddUntrackedTextureMemory(mClipMask); 880 } 881 mProfile.OnCacheMiss(); 882 return !!data; 883 } 884 885 bool DrawTargetWebgl::SetSimpleClipRect() { 886 // Determine whether the clipping rectangle is simple enough to accelerate. 887 // Check if there is a device space clip rectangle available from the Skia 888 // target. 889 if (Maybe<IntRect> clip = mSkia->GetDeviceClipRect(false)) { 890 // If the clip is empty, leave the final integer clip rectangle empty to 891 // trivially discard the draw request. 892 // If the clip rect is larger than the viewport, just set it to the 893 // viewport. 894 if (!clip->IsEmpty() && clip->Contains(GetRect())) { 895 clip = Some(GetRect()); 896 } 897 mSharedContext->SetClipRect(*clip); 898 mSharedContext->SetNoClipMask(); 899 return true; 900 } 901 902 // There was no pixel-aligned clip rect available, so check the clip stack to 903 // see if there is an AA'd axis-aligned rectangle clip. 904 Rect rect(GetRect()); 905 for (auto& clipStack : mClipStack) { 906 // If clip is a path or it has a non-axis-aligned transform, then it is 907 // complex. 908 if (clipStack.mPath || 909 !clipStack.mTransform.PreservesAxisAlignedRectangles()) { 910 return false; 911 } 912 // Transform the rect and intersect it with the current clip. 913 rect = 914 clipStack.mTransform.TransformBounds(clipStack.mRect).Intersect(rect); 915 } 916 mSharedContext->SetClipRect(rect); 917 mSharedContext->SetNoClipMask(); 918 return true; 919 } 920 921 // Installs the Skia clip rectangle, if applicable, onto the shared WebGL 922 // context as well as sets the WebGL framebuffer to the current target. 923 bool DrawTargetWebgl::PrepareContext(bool aClipped, 924 const RefPtr<TextureHandle>& aHandle, 925 const IntSize& aViewportSize) { 926 if (!aClipped || aHandle) { 927 // If no clipping requested, just set the clip rect to the viewport. 928 mSharedContext->SetClipRect( 929 aHandle 930 ? IntRect(IntPoint(), !aViewportSize.IsEmpty() 931 ? Min(aHandle->GetSize(), aViewportSize) 932 : aHandle->GetSize()) 933 : GetRect()); 934 mSharedContext->SetNoClipMask(); 935 // Ensure the clip gets reset if clipping is later requested for the target. 936 mRefreshClipState = true; 937 } else if (mRefreshClipState || !mSharedContext->IsCurrentTarget(this)) { 938 // Try to use a simple clip rect if possible. Otherwise, fall back to 939 // generating a clip mask texture that can represent complex clip regions. 940 if (!SetSimpleClipRect() && !GenerateComplexClipMask()) { 941 return false; 942 } 943 mClipChanged = false; 944 mRefreshClipState = false; 945 } 946 return mSharedContext->SetTarget(this, aHandle, aViewportSize); 947 } 948 949 void SharedContextWebgl::RestoreCurrentTarget( 950 const RefPtr<WebGLTexture>& aClipMask) { 951 if (!mCurrentTarget) { 952 return; 953 } 954 mWebgl->BindFramebuffer( 955 LOCAL_GL_FRAMEBUFFER, 956 mTargetHandle ? mTargetFramebuffer : mCurrentTarget->mFramebuffer); 957 IntPoint offset = 958 mTargetHandle ? mTargetHandle->GetBounds().TopLeft() : IntPoint(0, 0); 959 mWebgl->Viewport(offset.x, offset.y, mViewportSize.width, 960 mViewportSize.height); 961 if (aClipMask) { 962 SetClipMask(aClipMask); 963 } 964 } 965 966 bool SharedContextWebgl::IsContextLost() const { 967 return !mWebgl || mWebgl->IsContextLost(); 968 } 969 970 // Signal to CanvasRenderingContext2D when the WebGL context is lost. 971 bool DrawTargetWebgl::IsValid() const { 972 return mSharedContext && !mSharedContext->IsContextLost(); 973 } 974 975 bool DrawTargetWebgl::CanCreate(const IntSize& aSize, SurfaceFormat aFormat) { 976 if (!gfxVars::UseAcceleratedCanvas2D()) { 977 return false; 978 } 979 980 if (!Factory::AllowedSurfaceSize(aSize)) { 981 return false; 982 } 983 984 // The interpretation of the min-size and max-size follows from the old 985 // SkiaGL prefs. First just ensure that the context is not unreasonably 986 // small. 987 static const int32_t kMinDimension = 16; 988 if (std::min(aSize.width, aSize.height) < kMinDimension) { 989 return false; 990 } 991 992 int32_t minSize = StaticPrefs::gfx_canvas_accelerated_min_size(); 993 if (aSize.width * aSize.height < minSize * minSize) { 994 return false; 995 } 996 997 // Maximum pref allows 3 different options: 998 // 0 means unlimited size, 999 // > 0 means use value as an absolute threshold, 1000 // < 0 means use the number of screen pixels as a threshold. 1001 int32_t maxSize = StaticPrefs::gfx_canvas_accelerated_max_size(); 1002 if (maxSize > 0) { 1003 if (std::max(aSize.width, aSize.height) > maxSize) { 1004 return false; 1005 } 1006 } else if (maxSize < 0) { 1007 // Default to historical mobile screen size of 980x480, like FishIEtank. 1008 // In addition, allow acceleration up to this size even if the screen is 1009 // smaller. A lot content expects this size to work well. See Bug 999841 1010 static const int32_t kScreenPixels = 980 * 480; 1011 1012 if (RefPtr<widget::Screen> screen = 1013 widget::ScreenManager::GetSingleton().GetPrimaryScreen()) { 1014 LayoutDeviceIntSize screenSize = screen->GetRect().Size(); 1015 if (aSize.width * aSize.height > 1016 std::max(screenSize.width * screenSize.height, kScreenPixels)) { 1017 return false; 1018 } 1019 } 1020 } 1021 1022 return true; 1023 } 1024 1025 already_AddRefed<DrawTargetWebgl> DrawTargetWebgl::Create( 1026 const IntSize& aSize, SurfaceFormat aFormat, 1027 const RefPtr<SharedContextWebgl>& aSharedContext) { 1028 // Validate the size and format. 1029 if (!CanCreate(aSize, aFormat)) { 1030 return nullptr; 1031 } 1032 1033 RefPtr<DrawTargetWebgl> dt = new DrawTargetWebgl; 1034 if (!dt->Init(aSize, aFormat, aSharedContext) || !dt->IsValid()) { 1035 return nullptr; 1036 } 1037 1038 return dt.forget(); 1039 } 1040 1041 void* DrawTargetWebgl::GetNativeSurface(NativeSurfaceType aType) { 1042 switch (aType) { 1043 case NativeSurfaceType::WEBGL_CONTEXT: 1044 // If the context is lost, then don't attempt to access it. 1045 if (mSharedContext->IsContextLost()) { 1046 return nullptr; 1047 } 1048 if (!mWebglValid) { 1049 FlushFromSkia(); 1050 } 1051 return mSharedContext->mWebgl.get(); 1052 default: 1053 return nullptr; 1054 } 1055 } 1056 1057 // Wrap a WebGL texture holding a snapshot with a texture handle. Note that 1058 // while the texture is still in use as the backing texture of a framebuffer, 1059 // it's texture memory is not currently tracked with other texture handles. 1060 // Once it is finally orphaned and used as a texture handle, it must be added 1061 // to the resource usage totals. 1062 already_AddRefed<TextureHandle> SharedContextWebgl::WrapSnapshot( 1063 const IntSize& aSize, SurfaceFormat aFormat, RefPtr<WebGLTexture> aTex) { 1064 // Ensure there is enough space for the texture. 1065 size_t usedBytes = BackingTexture::UsedBytes(aFormat, aSize); 1066 PruneTextureMemory(usedBytes, false); 1067 // Allocate a handle for the texture 1068 RefPtr<StandaloneTexture> handle = 1069 new StandaloneTexture(aSize, aFormat, aTex.forget()); 1070 mStandaloneTextures.push_back(handle); 1071 mTextureHandles.insertFront(handle); 1072 AddTextureMemory(handle); 1073 mUsedTextureMemory += usedBytes; 1074 ++mNumTextureHandles; 1075 return handle.forget(); 1076 } 1077 1078 void SharedContextWebgl::SetTexFilter(WebGLTexture* aTex, bool aFilter) { 1079 mWebgl->TexParameter_base( 1080 LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, 1081 FloatOrInt(aFilter ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST)); 1082 mWebgl->TexParameter_base( 1083 LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, 1084 FloatOrInt(aFilter ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST)); 1085 } 1086 1087 void SharedContextWebgl::InitTexParameters(WebGLTexture* aTex, bool aFilter) { 1088 mWebgl->TexParameter_base(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, 1089 FloatOrInt(LOCAL_GL_REPEAT)); 1090 mWebgl->TexParameter_base(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, 1091 FloatOrInt(LOCAL_GL_REPEAT)); 1092 SetTexFilter(aTex, aFilter); 1093 } 1094 1095 // Copy the contents of the WebGL framebuffer into a WebGL texture. 1096 already_AddRefed<TextureHandle> SharedContextWebgl::CopySnapshot( 1097 const IntRect& aRect, TextureHandle* aHandle) { 1098 if (!mWebgl || mWebgl->IsContextLost()) { 1099 return nullptr; 1100 } 1101 1102 // If the target is going away, then we can just directly reuse the 1103 // framebuffer texture since it will never change. 1104 RefPtr<WebGLTexture> tex = mWebgl->CreateTexture(); 1105 if (!tex) { 1106 return nullptr; 1107 } 1108 1109 // If copying from a non-DT source, we have to bind a scratch framebuffer for 1110 // reading. 1111 if (aHandle) { 1112 BindScratchFramebuffer(aHandle, false); 1113 } 1114 1115 // Create a texture to hold the copy 1116 BindAndInitRenderTex(tex, SurfaceFormat::B8G8R8A8, aRect.Size()); 1117 // Copy the framebuffer into the texture 1118 mWebgl->CopyTexImage(LOCAL_GL_TEXTURE_2D, 0, 0, {0, 0, 0}, {aRect.x, aRect.y}, 1119 {uint32_t(aRect.width), uint32_t(aRect.height)}); 1120 1121 SurfaceFormat format = 1122 aHandle ? aHandle->GetFormat() : mCurrentTarget->GetFormat(); 1123 already_AddRefed<TextureHandle> result = 1124 WrapSnapshot(aRect.Size(), format, tex.forget()); 1125 1126 // Restore the actual framebuffer after reading is done. 1127 if (aHandle) { 1128 RestoreCurrentTarget(); 1129 } 1130 1131 return result; 1132 } 1133 1134 inline DrawTargetWebgl::AutoRestoreContext::AutoRestoreContext( 1135 DrawTargetWebgl* aTarget) 1136 : mTarget(aTarget), 1137 mClipAARect(aTarget->mSharedContext->mClipAARect), 1138 mLastClipMask(aTarget->mSharedContext->mLastClipMask) {} 1139 1140 inline DrawTargetWebgl::AutoRestoreContext::~AutoRestoreContext() { 1141 mTarget->mSharedContext->SetClipRect(mClipAARect); 1142 if (mLastClipMask) { 1143 mTarget->mSharedContext->SetClipMask(mLastClipMask); 1144 } 1145 mTarget->mRefreshClipState = true; 1146 } 1147 1148 // Utility method to install the target before copying a snapshot. 1149 already_AddRefed<TextureHandle> DrawTargetWebgl::CopySnapshot( 1150 const IntRect& aRect) { 1151 AutoRestoreContext restore(this); 1152 if (!PrepareContext(false)) { 1153 return nullptr; 1154 } 1155 return mSharedContext->CopySnapshot(aRect); 1156 } 1157 1158 bool DrawTargetWebgl::HasDataSnapshot() const { 1159 return (mSkiaValid && !mSkiaLayer) || (mSnapshot && mSnapshot->HasReadData()); 1160 } 1161 1162 bool DrawTargetWebgl::PrepareSkia() { 1163 if (!mSkiaValid) { 1164 ReadIntoSkia(); 1165 } else if (mSkiaLayer) { 1166 FlattenSkia(); 1167 } 1168 return mSkiaValid; 1169 } 1170 1171 bool DrawTargetWebgl::EnsureDataSnapshot() { 1172 // If there is already a data snapshot, there is nothing to do. If there is a 1173 // snapshot that has a pending PBO readback, then try to force the readback. 1174 // Otherwise, read back the WebGL framebuffer into the Skia DT. 1175 return HasDataSnapshot() || (mSnapshot && mSnapshot->ForceReadFromPBO()) || 1176 PrepareSkia(); 1177 } 1178 1179 void DrawTargetWebgl::PrepareShmem() { PrepareSkia(); } 1180 1181 // Borrow a snapshot that may be used by another thread for composition. Only 1182 // Skia snapshots are safe to pass around. 1183 already_AddRefed<SourceSurface> DrawTargetWebgl::GetDataSnapshot() { 1184 PrepareSkia(); 1185 return mSkia->Snapshot(mFormat); 1186 } 1187 1188 already_AddRefed<SourceSurface> DrawTargetWebgl::Snapshot() { 1189 // If already using the Skia fallback, then just snapshot that. 1190 if (mSkiaValid) { 1191 return GetDataSnapshot(); 1192 } 1193 1194 // There's no valid Skia snapshot, so we need to get one from the WebGL 1195 // context. 1196 if (!mSnapshot) { 1197 // Create a copy-on-write reference to this target. 1198 mSnapshot = new SourceSurfaceWebgl(this); 1199 } 1200 return do_AddRef(mSnapshot); 1201 } 1202 1203 // If we need to provide a snapshot for another DrawTargetWebgl that shares the 1204 // same WebGL context, then it is safe to directly return a snapshot. Otherwise, 1205 // we may be exporting to another thread and require a data snapshot. 1206 already_AddRefed<SourceSurface> DrawTargetWebgl::GetOptimizedSnapshot( 1207 DrawTarget* aTarget) { 1208 if (aTarget && aTarget->GetBackendType() == BackendType::WEBGL && 1209 static_cast<DrawTargetWebgl*>(aTarget)->mSharedContext == 1210 mSharedContext) { 1211 return Snapshot(); 1212 } 1213 return GetDataSnapshot(); 1214 } 1215 1216 // Read from the WebGL context into a buffer, either a memory buffer or a PBO. 1217 // This handles both swizzling BGRA to RGBA and flipping the image. 1218 bool SharedContextWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride, 1219 SurfaceFormat aFormat, const IntRect& aBounds, 1220 TextureHandle* aHandle, 1221 const RefPtr<WebGLBuffer>& aBuffer) { 1222 MOZ_ASSERT(aFormat == SurfaceFormat::B8G8R8A8 || 1223 aFormat == SurfaceFormat::B8G8R8X8 || 1224 aFormat == SurfaceFormat::A8); 1225 1226 // If reading into a new texture, we have to bind it to a scratch framebuffer 1227 // for reading. 1228 if (aHandle) { 1229 BindScratchFramebuffer(aHandle, false); 1230 } else if (!aBuffer && mCurrentTarget && !mTargetHandle && 1231 mCurrentTarget->mIsClear) { 1232 // If reading from a target that is still clear, then avoid the readback by 1233 // just clearing the data. 1234 SkPixmap(MakeSkiaImageInfo(aBounds.Size(), aFormat), aDstData, aDstStride) 1235 .erase(IsOpaque(aFormat) ? SK_ColorBLACK : SK_ColorTRANSPARENT); 1236 return true; 1237 } 1238 1239 webgl::ReadPixelsDesc desc; 1240 desc.srcOffset = *ivec2::From(aBounds); 1241 desc.size = *uvec2::FromSize(aBounds); 1242 desc.packState.rowLength = aDstStride / BytesPerPixel(aFormat); 1243 if (aBuffer) { 1244 mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, aBuffer); 1245 mWebgl->ReadPixelsPbo(desc, 0); 1246 mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, 0); 1247 } else { 1248 Range<uint8_t> range = {aDstData, size_t(aDstStride) * aBounds.height}; 1249 mWebgl->ReadPixelsInto(desc, range); 1250 } 1251 1252 // Restore the actual framebuffer after reading is done. 1253 if (aHandle) { 1254 RestoreCurrentTarget(); 1255 } 1256 1257 return true; 1258 } 1259 1260 already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshot( 1261 TextureHandle* aHandle, uint8_t* aData, int32_t aStride) { 1262 // Allocate a data surface, map it, and read from the WebGL context into the 1263 // surface. 1264 SurfaceFormat format = SurfaceFormat::UNKNOWN; 1265 IntRect bounds; 1266 if (aHandle) { 1267 format = aHandle->GetFormat(); 1268 bounds = aHandle->GetBounds(); 1269 } else { 1270 if (!mCurrentTarget) { 1271 return nullptr; 1272 } 1273 format = mCurrentTarget->GetFormat(); 1274 bounds = mCurrentTarget->GetRect(); 1275 } 1276 RefPtr<DataSourceSurface> surface = 1277 aData ? Factory::CreateWrappingDataSourceSurface(aData, aStride, 1278 bounds.Size(), format) 1279 : Factory::CreateDataSourceSurface(bounds.Size(), format); 1280 if (!surface) { 1281 return nullptr; 1282 } 1283 DataSourceSurface::ScopedMap dstMap(surface, DataSourceSurface::WRITE); 1284 if (!dstMap.IsMapped() || !ReadInto(dstMap.GetData(), dstMap.GetStride(), 1285 format, bounds, aHandle)) { 1286 return nullptr; 1287 } 1288 return surface.forget(); 1289 } 1290 1291 static inline int32_t GetPBOStride(int32_t aWidth, SurfaceFormat aFormat) { 1292 return GetAlignedStride<16>(aWidth, BytesPerPixel(aFormat)); 1293 } 1294 1295 already_AddRefed<WebGLBuffer> SharedContextWebgl::ReadSnapshotIntoPBO( 1296 SourceSurfaceWebgl* aOwner, TextureHandle* aHandle) { 1297 // Allocate a PBO, and read from the WebGL context into it. 1298 SurfaceFormat format = SurfaceFormat::UNKNOWN; 1299 IntRect bounds; 1300 if (aHandle) { 1301 format = aHandle->GetFormat(); 1302 bounds = aHandle->GetBounds(); 1303 } else { 1304 if (!mCurrentTarget) { 1305 return nullptr; 1306 } 1307 format = mCurrentTarget->GetFormat(); 1308 bounds = mCurrentTarget->GetRect(); 1309 } 1310 int32_t pboStride = GetPBOStride(bounds.width, format); 1311 size_t bufSize = BufferSizeFromStrideAndHeight(pboStride, bounds.height); 1312 if (!bufSize) { 1313 return nullptr; 1314 } 1315 1316 // If the PBO is too large to fit within the memory limit by itself, then 1317 // don't try to use a PBO. 1318 size_t maxPBOMemory = 1319 StaticPrefs::gfx_canvas_accelerated_max_snapshot_pbo_memory(); 1320 if (bufSize > maxPBOMemory) { 1321 return nullptr; 1322 } 1323 1324 RefPtr<WebGLBuffer> pbo = mWebgl->CreateBuffer(); 1325 if (!pbo) { 1326 return nullptr; 1327 } 1328 mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pbo); 1329 mWebgl->UninitializedBufferData_SizeOnly(LOCAL_GL_PIXEL_PACK_BUFFER, bufSize, 1330 LOCAL_GL_STREAM_READ); 1331 mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, 0); 1332 if (!ReadInto(nullptr, pboStride, format, bounds, aHandle, pbo)) { 1333 return nullptr; 1334 } 1335 1336 // If there are existing snapshot PBOs, check if adding this PBO would exceed 1337 // the memory limit for snapshot PBOs. This happens after the new PBO was set 1338 // up and the readback initiated, in case purging an old PBO causes a stall 1339 // which can be used to cover the latency of the readback for the new PBO. 1340 ClearSnapshotPBOs(maxPBOMemory - std::min(bufSize, maxPBOMemory)); 1341 1342 mUsedSnapshotPBOMemory += bufSize; 1343 mSnapshotPBOs.emplace_back(aOwner); 1344 return pbo.forget(); 1345 } 1346 1347 already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshotFromPBO( 1348 const RefPtr<WebGLBuffer>& aBuffer, SurfaceFormat aFormat, 1349 const IntSize& aSize, uint8_t* aData, int32_t aStride) { 1350 // For an existing PBO where a readback has been initiated previously, create 1351 // a new data surface and copy the PBO's data into the data surface. 1352 int32_t pboStride = GetPBOStride(aSize.width, aFormat); 1353 size_t bufSize = 1354 BufferSizeFromStrideAndHeight(aData ? aStride : pboStride, aSize.height); 1355 if (!bufSize) { 1356 return nullptr; 1357 } 1358 RefPtr<DataSourceSurface> surface = 1359 aData ? Factory::CreateWrappingDataSourceSurface(aData, aStride, aSize, 1360 aFormat) 1361 : Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat, 1362 pboStride); 1363 if (!surface) { 1364 return nullptr; 1365 } 1366 DataSourceSurface::ScopedMap dstMap(surface, DataSourceSurface::WRITE); 1367 if (!dstMap.IsMapped()) { 1368 return nullptr; 1369 } 1370 mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, aBuffer); 1371 Range<uint8_t> range = {dstMap.GetData(), bufSize}; 1372 bool success = mWebgl->AsWebGL2()->GetBufferSubData( 1373 LOCAL_GL_PIXEL_PACK_BUFFER, 0, range, aSize.height, 1374 BytesPerPixel(aFormat) * aSize.height, pboStride, dstMap.GetStride()); 1375 mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, 0); 1376 if (success) { 1377 return surface.forget(); 1378 } 1379 return nullptr; 1380 } 1381 1382 void SharedContextWebgl::RemoveSnapshotPBO( 1383 SourceSurfaceWebgl* aOwner, already_AddRefed<WebGLBuffer> aBuffer) { 1384 RefPtr<WebGLBuffer> buffer(aBuffer); 1385 MOZ_ASSERT(aOwner && buffer); 1386 IntSize size = aOwner->GetSize(); 1387 SurfaceFormat format = aOwner->GetFormat(); 1388 int32_t pboStride = GetPBOStride(size.width, format); 1389 size_t bufSize = BufferSizeFromStrideAndHeight(pboStride, size.height); 1390 // If the queue is empty, no memory should be used. Otherwise, deduct the 1391 // usage from the queue. 1392 if (mSnapshotPBOs.empty()) { 1393 mUsedSnapshotPBOMemory = 0; 1394 } else if (bufSize) { 1395 mUsedSnapshotPBOMemory -= std::min(mUsedSnapshotPBOMemory, bufSize); 1396 } 1397 } 1398 1399 void SharedContextWebgl::ClearSnapshotPBOs(size_t aMaxMemory) { 1400 // Force any pending readback PBOs to convert to actual data. 1401 while (!mSnapshotPBOs.empty() && 1402 (!aMaxMemory || mUsedSnapshotPBOMemory > aMaxMemory)) { 1403 RefPtr<SourceSurfaceWebgl> snapshot(mSnapshotPBOs.front()); 1404 mSnapshotPBOs.pop_front(); 1405 if (snapshot) { 1406 snapshot->ForceReadFromPBO(); 1407 } 1408 } 1409 if (mSnapshotPBOs.empty()) { 1410 mUsedSnapshotPBOMemory = 0; 1411 } 1412 } 1413 1414 // Utility method to install the target before reading a snapshot. 1415 bool DrawTargetWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride) { 1416 if (!PrepareContext(false)) { 1417 return false; 1418 } 1419 1420 return mSharedContext->ReadInto(aDstData, aDstStride, GetFormat(), GetRect()); 1421 } 1422 1423 // Utility method to install the target before reading a snapshot. 1424 already_AddRefed<DataSourceSurface> DrawTargetWebgl::ReadSnapshot( 1425 uint8_t* aData, int32_t aStride) { 1426 AutoRestoreContext restore(this); 1427 if (!PrepareContext(false)) { 1428 return nullptr; 1429 } 1430 mProfile.OnReadback(); 1431 return mSharedContext->ReadSnapshot(nullptr, aData, aStride); 1432 } 1433 1434 already_AddRefed<WebGLBuffer> DrawTargetWebgl::ReadSnapshotIntoPBO( 1435 SourceSurfaceWebgl* aOwner) { 1436 AutoRestoreContext restore(this); 1437 if (!PrepareContext(false)) { 1438 return nullptr; 1439 } 1440 mProfile.OnReadback(); 1441 return mSharedContext->ReadSnapshotIntoPBO(aOwner); 1442 } 1443 1444 already_AddRefed<SourceSurface> DrawTargetWebgl::GetBackingSurface() { 1445 return Snapshot(); 1446 } 1447 1448 void DrawTargetWebgl::DetachAllSnapshots() { 1449 mSkia->DetachAllSnapshots(); 1450 ClearSnapshot(); 1451 } 1452 1453 // Prepare the framebuffer for accelerated drawing. Any cached snapshots will 1454 // be invalidated if not detached and copied here. Ensure the WebGL 1455 // framebuffer's contents are updated if still somehow stored in the Skia 1456 // framebuffer. 1457 bool DrawTargetWebgl::MarkChanged() { 1458 if (mSnapshot) { 1459 // Try to copy the target into a new texture if possible. 1460 ClearSnapshot(true, true); 1461 } 1462 if (!mWebglValid && !FlushFromSkia()) { 1463 return false; 1464 } 1465 mSkiaValid = false; 1466 mIsClear = false; 1467 return true; 1468 } 1469 1470 void DrawTargetWebgl::MarkSkiaChanged(bool aOverwrite) { 1471 if (aOverwrite) { 1472 mSkiaValid = true; 1473 mSkiaLayer = false; 1474 } else if (!mSkiaValid) { 1475 if (ReadIntoSkia()) { 1476 // Signal that we've hit a complete software fallback. 1477 mProfile.OnFallback(); 1478 } 1479 } else if (mSkiaLayer && !mLayerDepth) { 1480 FlattenSkia(); 1481 } 1482 mWebglValid = false; 1483 mIsClear = false; 1484 } 1485 1486 // Whether a given composition operator is associative and thus allows drawing 1487 // into a separate layer that can be later composited back into the WebGL 1488 // context. 1489 static inline bool SupportsLayering(const DrawOptions& aOptions) { 1490 switch (aOptions.mCompositionOp) { 1491 case CompositionOp::OP_OVER: 1492 // Layering is only supported for the default source-over composition op. 1493 return true; 1494 default: 1495 return false; 1496 } 1497 } 1498 1499 void DrawTargetWebgl::MarkSkiaChanged(const DrawOptions& aOptions) { 1500 if (SupportsLayering(aOptions)) { 1501 if (!mSkiaValid) { 1502 // If the Skia context needs initialization, clear it and enable layering. 1503 mSkiaValid = true; 1504 if (mWebglValid) { 1505 mProfile.OnLayer(); 1506 mSkiaLayer = true; 1507 mSkiaLayerClear = mIsClear; 1508 mSkia->DetachAllSnapshots(); 1509 if (mSkiaLayerClear) { 1510 // Avoid blending later by making sure the layer background is filled 1511 // with opaque alpha values if necessary. 1512 mSkiaNoClip->FillRect(Rect(mSkiaNoClip->GetRect()), GetClearPattern(), 1513 DrawOptions(1.0f, CompositionOp::OP_SOURCE)); 1514 } else { 1515 mSkiaNoClip->ClearRect(Rect(mSkiaNoClip->GetRect())); 1516 } 1517 } 1518 } 1519 // The WebGL context is no longer up-to-date. 1520 mWebglValid = false; 1521 mIsClear = false; 1522 } else { 1523 // For other composition ops, just overwrite the Skia data. 1524 MarkSkiaChanged(); 1525 } 1526 } 1527 1528 bool DrawTargetWebgl::LockBits(uint8_t** aData, IntSize* aSize, 1529 int32_t* aStride, SurfaceFormat* aFormat, 1530 IntPoint* aOrigin) { 1531 // Can only access pixels if there is valid, flattened Skia data. 1532 if (mSkiaValid && !mSkiaLayer) { 1533 MarkSkiaChanged(); 1534 return mSkia->LockBits(aData, aSize, aStride, aFormat, aOrigin); 1535 } 1536 return false; 1537 } 1538 1539 void DrawTargetWebgl::ReleaseBits(uint8_t* aData) { 1540 // Can only access pixels if there is valid, flattened Skia data. 1541 if (mSkiaValid && !mSkiaLayer) { 1542 mSkia->ReleaseBits(aData); 1543 } 1544 } 1545 1546 // Format is x, y, alpha 1547 static const float kRectVertexData[12] = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1548 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f}; 1549 1550 // Orphans the contents of the path vertex buffer. The beginning of the buffer 1551 // always contains data for a simple rectangle draw to avoid needing to switch 1552 // buffers. 1553 void SharedContextWebgl::ResetPathVertexBuffer() { 1554 if (!mPathVertexBuffer) { 1555 MOZ_ASSERT(false); 1556 return; 1557 } 1558 1559 size_t oldCapacity = mPathVertexBuffer->ByteLength(); 1560 RemoveUntrackedTextureMemory(oldCapacity); 1561 1562 mWebgl->BindBuffer(LOCAL_GL_ARRAY_BUFFER, mPathVertexBuffer.get()); 1563 mWebgl->UninitializedBufferData_SizeOnly( 1564 LOCAL_GL_ARRAY_BUFFER, 1565 std::max(size_t(mPathVertexCapacity), sizeof(kRectVertexData)), 1566 LOCAL_GL_DYNAMIC_DRAW); 1567 mWebgl->BufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, sizeof(kRectVertexData), 1568 (const uint8_t*)kRectVertexData); 1569 mPathVertexOffset = sizeof(kRectVertexData); 1570 1571 size_t newCapacity = mPathVertexBuffer->ByteLength(); 1572 AddUntrackedTextureMemory(newCapacity); 1573 1574 if (mWGROutputBuffer && 1575 (!mPathVertexCapacity || newCapacity != oldCapacity)) { 1576 RemoveHeapData(mWGROutputBuffer.get()); 1577 mWGROutputBuffer = nullptr; 1578 } 1579 1580 if (mPathVertexCapacity > 0 && !mWGROutputBuffer) { 1581 mWGROutputBuffer.reset(new ( 1582 fallible) WGR::OutputVertex[newCapacity / sizeof(WGR::OutputVertex)]); 1583 AddHeapData(mWGROutputBuffer.get()); 1584 } 1585 } 1586 1587 // Sigma below which contribution of neighbor pixels is visually insignificant. 1588 #define BLUR_ACCEL_SIGMA_MIN 0.27f 1589 // Maximum blur sigma allowed by shadows and filters currently. 1590 #define BLUR_ACCEL_SIGMA_MAX 100 1591 #define BLUR_ACCEL_RADIUS(sigma) (int(ceil(1.5 * (sigma))) * 2) 1592 #define BLUR_ACCEL_RADIUS_MAX (3 * BLUR_ACCEL_SIGMA_MAX) 1593 1594 // Threshold for when to downscale blur inputs. 1595 #define BLUR_ACCEL_DOWNSCALE_SIGMA 20 1596 #define BLUR_ACCEL_DOWNSCALE_SIZE 32 1597 // How much to downscale blur inputs. 1598 #define BLUR_ACCEL_DOWNSCALE_ITERS 2 1599 1600 // Attempts to create all shaders and resources to be used for drawing commands. 1601 // Returns whether or not this succeeded. 1602 bool SharedContextWebgl::CreateShaders() { 1603 if (!mPathVertexArray) { 1604 mPathVertexArray = mWebgl->CreateVertexArray(); 1605 } 1606 if (!mPathVertexBuffer) { 1607 mPathVertexBuffer = mWebgl->CreateBuffer(); 1608 AddUntrackedTextureMemory(mPathVertexBuffer); 1609 mWebgl->BindVertexArray(mPathVertexArray.get()); 1610 ResetPathVertexBuffer(); 1611 mWebgl->EnableVertexAttribArray(0); 1612 1613 webgl::VertAttribPointerDesc attribDesc; 1614 attribDesc.channels = 3; 1615 attribDesc.type = LOCAL_GL_FLOAT; 1616 attribDesc.normalized = false; 1617 mWebgl->VertexAttribPointer(0, attribDesc); 1618 } 1619 if (!mSolidProgram) { 1620 // AA is computed by using the basis vectors of the transform to determine 1621 // both the scale and orientation. The scale is then used to extrude the 1622 // rectangle outward by 1 screen-space pixel to account for the AA region. 1623 // The distance to the rectangle edges is passed to the fragment shader in 1624 // an interpolant, biased by 0.5 so it represents the desired coverage. The 1625 // minimum coverage is then chosen by the fragment shader to use as an AA 1626 // coverage value to modulate the color. 1627 auto vsSource = 1628 "attribute vec3 a_vertex;\n" 1629 "uniform vec2 u_transform[3];\n" 1630 "uniform vec2 u_viewport;\n" 1631 "uniform vec4 u_clipbounds;\n" 1632 "uniform float u_aa;\n" 1633 "varying vec2 v_cliptc;\n" 1634 "varying vec4 v_clipdist;\n" 1635 "varying vec4 v_dist;\n" 1636 "varying float v_alpha;\n" 1637 "void main() {\n" 1638 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n" 1639 " dot(u_transform[1], u_transform[1]));\n" 1640 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n" 1641 " scale *= invScale;\n" 1642 " vec2 extrude = a_vertex.xy +\n" 1643 " invScale * (2.0 * a_vertex.xy - 1.0);\n" 1644 " vec2 vertex = u_transform[0] * extrude.x +\n" 1645 " u_transform[1] * extrude.y +\n" 1646 " u_transform[2];\n" 1647 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n" 1648 " v_cliptc = vertex / u_viewport;\n" 1649 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n" 1650 " u_clipbounds.zw - vertex);\n" 1651 " float noAA = 1.0 - u_aa;\n" 1652 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 0.5 + noAA;\n" 1653 " v_alpha = min(a_vertex.z,\n" 1654 " min(scale.x, 1.0) * min(scale.y, 1.0) + noAA);\n" 1655 "}\n"; 1656 auto fsSource = 1657 "precision mediump float;\n" 1658 "uniform vec4 u_color;\n" 1659 "uniform sampler2D u_clipmask;\n" 1660 "varying highp vec2 v_cliptc;\n" 1661 "varying vec4 v_clipdist;\n" 1662 "varying vec4 v_dist;\n" 1663 "varying float v_alpha;\n" 1664 "void main() {\n" 1665 " float clip = texture2D(u_clipmask, v_cliptc).r;\n" 1666 " vec4 dist = min(v_dist, v_clipdist);\n" 1667 " dist.xy = min(dist.xy, dist.zw);\n" 1668 " float aa = clamp(min(dist.x, dist.y), 0.0, v_alpha);\n" 1669 " gl_FragColor = clip * aa * u_color;\n" 1670 "}\n"; 1671 RefPtr<WebGLShader> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER); 1672 mWebgl->ShaderSource(*vsId, vsSource); 1673 mWebgl->CompileShader(*vsId); 1674 if (!mWebgl->GetCompileResult(*vsId).success) { 1675 return false; 1676 } 1677 RefPtr<WebGLShader> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER); 1678 mWebgl->ShaderSource(*fsId, fsSource); 1679 mWebgl->CompileShader(*fsId); 1680 if (!mWebgl->GetCompileResult(*fsId).success) { 1681 return false; 1682 } 1683 mSolidProgram = mWebgl->CreateProgram(); 1684 mWebgl->AttachShader(*mSolidProgram, *vsId); 1685 mWebgl->AttachShader(*mSolidProgram, *fsId); 1686 mWebgl->BindAttribLocation(*mSolidProgram, 0, "a_vertex"); 1687 mWebgl->LinkProgram(*mSolidProgram); 1688 if (!mWebgl->GetLinkResult(*mSolidProgram).success) { 1689 return false; 1690 } 1691 mSolidProgramViewport = GetUniformLocation(mSolidProgram, "u_viewport"); 1692 mSolidProgramAA = GetUniformLocation(mSolidProgram, "u_aa"); 1693 mSolidProgramTransform = GetUniformLocation(mSolidProgram, "u_transform"); 1694 mSolidProgramColor = GetUniformLocation(mSolidProgram, "u_color"); 1695 mSolidProgramClipMask = GetUniformLocation(mSolidProgram, "u_clipmask"); 1696 mSolidProgramClipBounds = GetUniformLocation(mSolidProgram, "u_clipbounds"); 1697 if (!mSolidProgramViewport || !mSolidProgramAA || !mSolidProgramTransform || 1698 !mSolidProgramColor || !mSolidProgramClipMask || 1699 !mSolidProgramClipBounds) { 1700 return false; 1701 } 1702 mWebgl->UseProgram(mSolidProgram); 1703 UniformData(LOCAL_GL_INT, mSolidProgramClipMask, Array<int32_t, 1>{1}); 1704 } 1705 1706 if (!mImageProgram) { 1707 auto vsSource = 1708 "attribute vec3 a_vertex;\n" 1709 "uniform vec2 u_viewport;\n" 1710 "uniform vec4 u_clipbounds;\n" 1711 "uniform float u_aa;\n" 1712 "uniform vec2 u_transform[3];\n" 1713 "uniform vec2 u_texmatrix[3];\n" 1714 "varying vec2 v_cliptc;\n" 1715 "varying vec2 v_texcoord;\n" 1716 "varying vec4 v_clipdist;\n" 1717 "varying vec4 v_dist;\n" 1718 "varying float v_alpha;\n" 1719 "void main() {\n" 1720 " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n" 1721 " dot(u_transform[1], u_transform[1]));\n" 1722 " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n" 1723 " scale *= invScale;\n" 1724 " vec2 extrude = a_vertex.xy +\n" 1725 " invScale * (2.0 * a_vertex.xy - 1.0);\n" 1726 " vec2 vertex = u_transform[0] * extrude.x +\n" 1727 " u_transform[1] * extrude.y +\n" 1728 " u_transform[2];\n" 1729 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n" 1730 " v_cliptc = vertex / u_viewport;\n" 1731 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n" 1732 " u_clipbounds.zw - vertex);\n" 1733 " v_texcoord = u_texmatrix[0] * extrude.x +\n" 1734 " u_texmatrix[1] * extrude.y +\n" 1735 " u_texmatrix[2];\n" 1736 " float noAA = 1.0 - u_aa;\n" 1737 " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 0.5 + noAA;\n" 1738 " v_alpha = min(a_vertex.z,\n" 1739 " min(scale.x, 1.0) * min(scale.y, 1.0) + noAA);\n" 1740 "}\n"; 1741 auto fsSource = 1742 "precision mediump float;\n" 1743 "uniform vec4 u_texbounds;\n" 1744 "uniform vec4 u_color;\n" 1745 "uniform float u_swizzle;\n" 1746 "uniform sampler2D u_sampler;\n" 1747 "uniform sampler2D u_clipmask;\n" 1748 "varying highp vec2 v_cliptc;\n" 1749 "varying highp vec2 v_texcoord;\n" 1750 "varying vec4 v_clipdist;\n" 1751 "varying vec4 v_dist;\n" 1752 "varying float v_alpha;\n" 1753 "void main() {\n" 1754 " highp vec2 tc = clamp(v_texcoord, u_texbounds.xy,\n" 1755 " u_texbounds.zw);\n" 1756 " vec4 image = texture2D(u_sampler, tc);\n" 1757 " float clip = texture2D(u_clipmask, v_cliptc).r;\n" 1758 " vec4 dist = min(v_dist, v_clipdist);\n" 1759 " dist.xy = min(dist.xy, dist.zw);\n" 1760 " float aa = clamp(min(dist.x, dist.y), 0.0, v_alpha);\n" 1761 " gl_FragColor = clip * aa * u_color *\n" 1762 " mix(image, image.rrrr, u_swizzle);\n" 1763 "}\n"; 1764 RefPtr<WebGLShader> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER); 1765 mWebgl->ShaderSource(*vsId, vsSource); 1766 mWebgl->CompileShader(*vsId); 1767 if (!mWebgl->GetCompileResult(*vsId).success) { 1768 return false; 1769 } 1770 RefPtr<WebGLShader> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER); 1771 mWebgl->ShaderSource(*fsId, fsSource); 1772 mWebgl->CompileShader(*fsId); 1773 if (!mWebgl->GetCompileResult(*fsId).success) { 1774 return false; 1775 } 1776 mImageProgram = mWebgl->CreateProgram(); 1777 mWebgl->AttachShader(*mImageProgram, *vsId); 1778 mWebgl->AttachShader(*mImageProgram, *fsId); 1779 mWebgl->BindAttribLocation(*mImageProgram, 0, "a_vertex"); 1780 mWebgl->LinkProgram(*mImageProgram); 1781 if (!mWebgl->GetLinkResult(*mImageProgram).success) { 1782 return false; 1783 } 1784 mImageProgramViewport = GetUniformLocation(mImageProgram, "u_viewport"); 1785 mImageProgramAA = GetUniformLocation(mImageProgram, "u_aa"); 1786 mImageProgramTransform = GetUniformLocation(mImageProgram, "u_transform"); 1787 mImageProgramTexMatrix = GetUniformLocation(mImageProgram, "u_texmatrix"); 1788 mImageProgramTexBounds = GetUniformLocation(mImageProgram, "u_texbounds"); 1789 mImageProgramSwizzle = GetUniformLocation(mImageProgram, "u_swizzle"); 1790 mImageProgramColor = GetUniformLocation(mImageProgram, "u_color"); 1791 mImageProgramSampler = GetUniformLocation(mImageProgram, "u_sampler"); 1792 mImageProgramClipMask = GetUniformLocation(mImageProgram, "u_clipmask"); 1793 mImageProgramClipBounds = GetUniformLocation(mImageProgram, "u_clipbounds"); 1794 if (!mImageProgramViewport || !mImageProgramAA || !mImageProgramTransform || 1795 !mImageProgramTexMatrix || !mImageProgramTexBounds || 1796 !mImageProgramSwizzle || !mImageProgramColor || !mImageProgramSampler || 1797 !mImageProgramClipMask || !mImageProgramClipBounds) { 1798 return false; 1799 } 1800 mWebgl->UseProgram(mImageProgram); 1801 UniformData(LOCAL_GL_INT, mImageProgramSampler, Array<int32_t, 1>{0}); 1802 UniformData(LOCAL_GL_INT, mImageProgramClipMask, Array<int32_t, 1>{1}); 1803 } 1804 if (!mBlurProgram) { 1805 auto vsSource = 1806 "#version 300 es\n" 1807 "uniform vec2 u_viewport;\n" 1808 "uniform vec4 u_clipbounds;\n" 1809 "uniform vec4 u_transform;\n" 1810 "uniform vec4 u_texmatrix;\n" 1811 "uniform vec4 u_texbounds;\n" 1812 "uniform vec2 u_offsetscale;\n" 1813 "uniform float u_sigma;\n" 1814 "in vec3 a_vertex;\n" 1815 "out vec2 v_cliptc;\n" 1816 "out vec2 v_texcoord;\n" 1817 "out vec4 v_texbounds;\n" 1818 "out vec4 v_clipdist;\n" 1819 "flat out vec2 v_gauss_coeffs;\n" 1820 "flat out ivec2 v_support;\n" 1821 "void calculate_gauss_coeffs(float sigma) {\n" 1822 " v_gauss_coeffs = vec2(1.0 / (sqrt(2.0 * 3.14159265) * sigma),\n" 1823 " exp(-0.5 / (sigma * sigma)));\n" 1824 " vec3 gauss_coeff = vec3(v_gauss_coeffs,\n" 1825 " v_gauss_coeffs.y * v_gauss_coeffs.y);\n" 1826 " float gauss_coeff_total = gauss_coeff.x;\n" 1827 " for (int i = 1; i <= v_support.x; i += 2) {\n" 1828 " gauss_coeff.xy *= gauss_coeff.yz;\n" 1829 " float gauss_coeff_subtotal = gauss_coeff.x;\n" 1830 " gauss_coeff.xy *= gauss_coeff.yz;\n" 1831 " gauss_coeff_subtotal += gauss_coeff.x;\n" 1832 " gauss_coeff_total += 2.0 * gauss_coeff_subtotal;\n" 1833 " }\n" 1834 " v_gauss_coeffs.x /= gauss_coeff_total;\n" 1835 "}\n" 1836 "void main() {\n" 1837 " vec2 vertex = u_transform.xy * a_vertex.xy + u_transform.zw;\n" 1838 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n" 1839 " v_cliptc = vertex / u_viewport;\n" 1840 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n" 1841 " u_clipbounds.zw - vertex);\n" 1842 " v_texcoord = u_texmatrix.xy * a_vertex.xy + u_texmatrix.zw;\n" 1843 " vec4 texbounds = vec4(v_texcoord - u_texbounds.xy,\n" 1844 " u_texbounds.zw - v_texcoord);\n" 1845 " v_texbounds = u_offsetscale.x != 0.0 ?\n" 1846 " vec4(texbounds.xz / u_offsetscale.x, texbounds.yw) :\n" 1847 " vec4(texbounds.yw / u_offsetscale.y, texbounds.xz);\n" 1848 " v_support.x = " MOZ_STRINGIFY(BLUR_ACCEL_RADIUS(u_sigma)) ";\n" 1849 " calculate_gauss_coeffs(u_sigma);\n" 1850 "}\n"; 1851 auto fsSource = 1852 "#version 300 es\n" 1853 "precision mediump float;\n" 1854 "uniform vec4 u_color;\n" 1855 "uniform float u_swizzle;\n" 1856 "uniform highp vec4 u_texbounds;\n" 1857 "uniform highp vec2 u_offsetscale;\n" 1858 "uniform sampler2D u_sampler;\n" 1859 "uniform sampler2D u_clipmask;\n" 1860 "in highp vec2 v_cliptc;\n" 1861 "in highp vec2 v_texcoord;\n" 1862 "in highp vec4 v_texbounds;\n" 1863 "in vec4 v_clipdist;\n" 1864 "flat in vec2 v_gauss_coeffs;\n" 1865 "flat in ivec2 v_support;\n" 1866 "out vec4 out_FragColor;\n" 1867 "void main() {\n" 1868 " vec3 gauss_coeff = vec3(v_gauss_coeffs,\n" 1869 " v_gauss_coeffs.y * v_gauss_coeffs.y);\n" 1870 " bvec4 inside = greaterThanEqual(v_texbounds, vec4(0.0));\n" 1871 " vec4 avg_color = texture(u_sampler, v_texcoord) *\n" 1872 " (all(inside.xy) ? gauss_coeff.x : 0.0);\n" 1873 " int support = min(v_support.x,\n" 1874 " " MOZ_STRINGIFY(BLUR_ACCEL_RADIUS_MAX) ");\n" 1875 " for (int i = 1; i <= support; i += 2) {\n" 1876 " gauss_coeff.xy *= gauss_coeff.yz;\n" 1877 " float gauss_coeff_subtotal = gauss_coeff.x;\n" 1878 " gauss_coeff.xy *= gauss_coeff.yz;\n" 1879 " gauss_coeff_subtotal += gauss_coeff.x;\n" 1880 " float gauss_ratio = gauss_coeff.x / gauss_coeff_subtotal;\n" 1881 " vec4 curbounds = v_texbounds.xyxy + vec4(-1.0, 1.0, 1.0, -1.0) * float(i);\n" 1882 " bvec4 inside0 = greaterThanEqual(curbounds.xyxy, vec4(0.0, 0.0, 1.0, -1.0));\n" 1883 " bvec4 inside1 = greaterThanEqual(curbounds.zwzw, vec4(0.0, 0.0, -1.0, 1.0));\n" 1884 " vec2 weights0 =\n" 1885 " (all(inside0.xy) ? vec2(1.0, gauss_ratio) : vec2(gauss_ratio, 1.0)) -\n" 1886 " (all(inside0.zw) ? 0.0 : gauss_ratio);\n" 1887 " vec2 weights1 =\n" 1888 " (all(inside1.xy) ? vec2(1.0, gauss_ratio) : vec2(gauss_ratio, 1.0)) -\n" 1889 " (all(inside1.zw) ? 0.0 : gauss_ratio);\n" 1890 " vec2 tc0 = v_texcoord - u_offsetscale * (float(i) + weights0.y);\n" 1891 " vec2 tc1 = v_texcoord + u_offsetscale * (float(i) + weights1.y);\n" 1892 " avg_color += gauss_coeff_subtotal * (\n" 1893 " texture(u_sampler, tc0) * weights0.x +\n" 1894 " texture(u_sampler, tc1) * weights1.x);\n" 1895 " }\n" 1896 " float clip = texture(u_clipmask, v_cliptc).r;\n" 1897 " vec2 dist = min(v_clipdist.xy, v_clipdist.zw);\n" 1898 " float aa = clamp(min(dist.x, dist.y), 0.0, 1.0);\n" 1899 " out_FragColor = clip * aa * u_color * float(all(inside.zw)) *\n" 1900 " mix(avg_color, avg_color.rrrr, u_swizzle);\n" 1901 "}\n"; 1902 RefPtr<WebGLShader> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER); 1903 mWebgl->ShaderSource(*vsId, vsSource); 1904 mWebgl->CompileShader(*vsId); 1905 if (!mWebgl->GetCompileResult(*vsId).success) { 1906 return false; 1907 } 1908 RefPtr<WebGLShader> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER); 1909 mWebgl->ShaderSource(*fsId, fsSource); 1910 mWebgl->CompileShader(*fsId); 1911 if (!mWebgl->GetCompileResult(*fsId).success) { 1912 return false; 1913 } 1914 mBlurProgram = mWebgl->CreateProgram(); 1915 mWebgl->AttachShader(*mBlurProgram, *vsId); 1916 mWebgl->AttachShader(*mBlurProgram, *fsId); 1917 mWebgl->BindAttribLocation(*mBlurProgram, 0, "a_vertex"); 1918 mWebgl->LinkProgram(*mBlurProgram); 1919 if (!mWebgl->GetLinkResult(*mBlurProgram).success) { 1920 return false; 1921 } 1922 mBlurProgramViewport = GetUniformLocation(mBlurProgram, "u_viewport"); 1923 mBlurProgramTransform = GetUniformLocation(mBlurProgram, "u_transform"); 1924 mBlurProgramTexMatrix = GetUniformLocation(mBlurProgram, "u_texmatrix"); 1925 mBlurProgramTexBounds = GetUniformLocation(mBlurProgram, "u_texbounds"); 1926 mBlurProgramOffsetScale = GetUniformLocation(mBlurProgram, "u_offsetscale"); 1927 mBlurProgramSigma = GetUniformLocation(mBlurProgram, "u_sigma"); 1928 mBlurProgramColor = GetUniformLocation(mBlurProgram, "u_color"); 1929 mBlurProgramSwizzle = GetUniformLocation(mBlurProgram, "u_swizzle"); 1930 mBlurProgramSampler = GetUniformLocation(mBlurProgram, "u_sampler"); 1931 mBlurProgramClipMask = GetUniformLocation(mBlurProgram, "u_clipmask"); 1932 mBlurProgramClipBounds = GetUniformLocation(mBlurProgram, "u_clipbounds"); 1933 if (!mBlurProgramViewport || !mBlurProgramTransform || 1934 !mBlurProgramTexMatrix || !mBlurProgramTexBounds || 1935 !mBlurProgramOffsetScale || !mBlurProgramSigma || !mBlurProgramColor || 1936 !mBlurProgramSwizzle || !mBlurProgramSampler || !mBlurProgramClipMask || 1937 !mBlurProgramClipBounds) { 1938 return false; 1939 } 1940 mWebgl->UseProgram(mBlurProgram); 1941 UniformData(LOCAL_GL_INT, mBlurProgramSampler, Array<int32_t, 1>{0}); 1942 UniformData(LOCAL_GL_INT, mBlurProgramClipMask, Array<int32_t, 1>{1}); 1943 } 1944 if (!mFilterProgram) { 1945 auto vsSource = 1946 "uniform vec2 u_viewport;\n" 1947 "uniform vec4 u_clipbounds;\n" 1948 "uniform vec4 u_transform;\n" 1949 "uniform vec4 u_texmatrix;\n" 1950 "attribute vec3 a_vertex;\n" 1951 "varying vec2 v_cliptc;\n" 1952 "varying vec2 v_texcoord;\n" 1953 "varying vec4 v_clipdist;\n" 1954 "void main() {\n" 1955 " vec2 vertex = u_transform.xy * a_vertex.xy + u_transform.zw;\n" 1956 " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n" 1957 " v_cliptc = vertex / u_viewport;\n" 1958 " v_clipdist = vec4(vertex - u_clipbounds.xy,\n" 1959 " u_clipbounds.zw - vertex);\n" 1960 " v_texcoord = u_texmatrix.xy * a_vertex.xy + u_texmatrix.zw;\n" 1961 "}\n"; 1962 auto fsSource = 1963 "precision mediump float;\n" 1964 "uniform vec4 u_texbounds;\n" 1965 "uniform mat4 u_colormatrix;\n" 1966 "uniform vec4 u_coloroffset;\n" 1967 "uniform sampler2D u_sampler;\n" 1968 "uniform sampler2D u_clipmask;\n" 1969 "varying highp vec2 v_cliptc;\n" 1970 "varying highp vec2 v_texcoord;\n" 1971 "varying vec4 v_clipdist;\n" 1972 "bool check_bounds(vec2 tc) {\n" 1973 " return all(greaterThanEqual(\n" 1974 " vec4(tc, u_texbounds.zw), vec4(u_texbounds.xy, tc)));\n" 1975 "}\n" 1976 "void main() {\n" 1977 " vec4 color = check_bounds(v_texcoord) ?\n" 1978 " texture2D(u_sampler, v_texcoord) : vec4(0.0);\n" 1979 " if (color.a != 0.0) color.rgb /= color.a;\n" 1980 " color = clamp(u_colormatrix * color + u_coloroffset, 0.0, 1.0);\n" 1981 " color.rgb *= color.a;\n" 1982 " float clip = texture2D(u_clipmask, v_cliptc).r;\n" 1983 " vec2 dist = min(v_clipdist.xy, v_clipdist.zw);\n" 1984 " float aa = clamp(min(dist.x, dist.y), 0.0, 1.0);\n" 1985 " gl_FragColor = clip * aa * color;\n" 1986 "}\n"; 1987 RefPtr<WebGLShader> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER); 1988 mWebgl->ShaderSource(*vsId, vsSource); 1989 mWebgl->CompileShader(*vsId); 1990 if (!mWebgl->GetCompileResult(*vsId).success) { 1991 return false; 1992 } 1993 RefPtr<WebGLShader> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER); 1994 mWebgl->ShaderSource(*fsId, fsSource); 1995 mWebgl->CompileShader(*fsId); 1996 if (!mWebgl->GetCompileResult(*fsId).success) { 1997 return false; 1998 } 1999 mFilterProgram = mWebgl->CreateProgram(); 2000 mWebgl->AttachShader(*mFilterProgram, *vsId); 2001 mWebgl->AttachShader(*mFilterProgram, *fsId); 2002 mWebgl->BindAttribLocation(*mFilterProgram, 0, "a_vertex"); 2003 mWebgl->LinkProgram(*mFilterProgram); 2004 if (!mWebgl->GetLinkResult(*mFilterProgram).success) { 2005 return false; 2006 } 2007 mFilterProgramViewport = GetUniformLocation(mFilterProgram, "u_viewport"); 2008 mFilterProgramTransform = GetUniformLocation(mFilterProgram, "u_transform"); 2009 mFilterProgramTexMatrix = GetUniformLocation(mFilterProgram, "u_texmatrix"); 2010 mFilterProgramTexBounds = GetUniformLocation(mFilterProgram, "u_texbounds"); 2011 mFilterProgramColorMatrix = 2012 GetUniformLocation(mFilterProgram, "u_colormatrix"); 2013 mFilterProgramColorOffset = 2014 GetUniformLocation(mFilterProgram, "u_coloroffset"); 2015 mFilterProgramSampler = GetUniformLocation(mFilterProgram, "u_sampler"); 2016 mFilterProgramClipMask = GetUniformLocation(mFilterProgram, "u_clipmask"); 2017 mFilterProgramClipBounds = 2018 GetUniformLocation(mFilterProgram, "u_clipbounds"); 2019 if (!mFilterProgramViewport || !mFilterProgramTransform || 2020 !mFilterProgramTexMatrix || !mFilterProgramTexBounds || 2021 !mFilterProgramColorMatrix || !mFilterProgramColorOffset || 2022 !mFilterProgramSampler || !mFilterProgramClipMask || 2023 !mFilterProgramClipBounds) { 2024 return false; 2025 } 2026 mWebgl->UseProgram(mFilterProgram); 2027 UniformData(LOCAL_GL_INT, mFilterProgramSampler, Array<int32_t, 1>{0}); 2028 UniformData(LOCAL_GL_INT, mFilterProgramClipMask, Array<int32_t, 1>{1}); 2029 } 2030 return true; 2031 } 2032 2033 void SharedContextWebgl::EnableScissor(const IntRect& aRect, bool aForce) { 2034 // Only update scissor state if it actually changes. 2035 IntRect rect = !aForce && mTargetHandle 2036 ? aRect + mTargetHandle->GetBounds().TopLeft() 2037 : aRect; 2038 if (!mLastScissor.IsEqualEdges(rect)) { 2039 mLastScissor = rect; 2040 mWebgl->Scissor(rect.x, rect.y, rect.width, rect.height); 2041 } 2042 if (!mScissorEnabled) { 2043 mScissorEnabled = true; 2044 mWebgl->SetEnabled(LOCAL_GL_SCISSOR_TEST, {}, true); 2045 } 2046 } 2047 2048 void SharedContextWebgl::DisableScissor(bool aForce) { 2049 if (!aForce && mTargetHandle) { 2050 EnableScissor(IntRect(IntPoint(), mViewportSize)); 2051 return; 2052 } 2053 if (mScissorEnabled) { 2054 mScissorEnabled = false; 2055 mWebgl->SetEnabled(LOCAL_GL_SCISSOR_TEST, {}, false); 2056 } 2057 } 2058 2059 inline ColorPattern DrawTargetWebgl::GetClearPattern() const { 2060 return ColorPattern( 2061 DeviceColor(0.0f, 0.0f, 0.0f, IsOpaque(mFormat) ? 1.0f : 0.0f)); 2062 } 2063 2064 template <typename R> 2065 inline RectDouble DrawTargetWebgl::TransformDouble(const R& aRect) const { 2066 return MatrixDouble(mTransform).TransformBounds(WidenToDouble(aRect)); 2067 } 2068 2069 // Check if the transformed rect clips to the viewport. 2070 inline Maybe<Rect> DrawTargetWebgl::RectClippedToViewport( 2071 const RectDouble& aRect) const { 2072 if (!mTransform.PreservesAxisAlignedRectangles()) { 2073 return Nothing(); 2074 } 2075 2076 return Some(NarrowToFloat(aRect.SafeIntersect(RectDouble(GetRect())))); 2077 } 2078 2079 // Ensure that the rect, after transform, is within reasonable precision limits 2080 // such that when transformed and clipped in the shader it will not round bits 2081 // from the mantissa in a way that will diverge in a noticeable way from path 2082 // geometry calculated by the path fallback. 2083 template <typename R> 2084 static inline bool RectInsidePrecisionLimits(const R& aRect) { 2085 return R(-(1 << 20), -(1 << 20), 2 << 20, 2 << 20).Contains(aRect); 2086 } 2087 2088 void DrawTargetWebgl::ClearRect(const Rect& aRect) { 2089 if (mIsClear) { 2090 // No need to clear anything if the entire framebuffer is already clear. 2091 return; 2092 } 2093 2094 RectDouble xformRect = TransformDouble(aRect); 2095 bool containsViewport = false; 2096 if (Maybe<Rect> clipped = RectClippedToViewport(xformRect)) { 2097 // If the rect clips to viewport, just clear the clipped rect 2098 // to avoid transform issues. 2099 containsViewport = clipped->Size() == Size(GetSize()); 2100 DrawRect(*clipped, GetClearPattern(), 2101 DrawOptions(1.0f, CompositionOp::OP_CLEAR), Nothing(), nullptr, 2102 false); 2103 } else if (RectInsidePrecisionLimits(xformRect)) { 2104 // If the rect transform won't stress precision, then just use it. 2105 DrawRect(aRect, GetClearPattern(), 2106 DrawOptions(1.0f, CompositionOp::OP_CLEAR)); 2107 } else { 2108 // Otherwise, using the transform in the shader may lead to inaccuracies, so 2109 // just fall back. 2110 MarkSkiaChanged(); 2111 mSkia->ClearRect(aRect); 2112 } 2113 2114 // If the clear rectangle encompasses the entire viewport and is not clipped, 2115 // then mark the target as entirely clear. 2116 if (containsViewport && mSharedContext->IsCurrentTarget(this) && 2117 !mSharedContext->HasClipMask() && 2118 mSharedContext->mClipAARect.Contains(Rect(GetRect()))) { 2119 mIsClear = true; 2120 } 2121 } 2122 2123 static inline DeviceColor PremultiplyColor(const DeviceColor& aColor, 2124 float aAlpha = 1.0f) { 2125 float a = aColor.a * aAlpha; 2126 return DeviceColor(aColor.r * a, aColor.g * a, aColor.b * a, a); 2127 } 2128 2129 // Attempts to create the framebuffer used for drawing and also any relevant 2130 // non-shared resources. Returns whether or not this succeeded. 2131 bool DrawTargetWebgl::CreateFramebuffer() { 2132 RefPtr<WebGLContext> webgl = mSharedContext->mWebgl; 2133 if (!mFramebuffer) { 2134 mFramebuffer = webgl->CreateFramebuffer(); 2135 } 2136 if (!mTex) { 2137 mTex = webgl->CreateTexture(); 2138 mSharedContext->BindAndInitRenderTex(mTex, SurfaceFormat::B8G8R8A8, mSize); 2139 webgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mFramebuffer); 2140 webgl::FbAttachInfo attachInfo; 2141 attachInfo.tex = mTex; 2142 webgl->FramebufferAttach(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, 2143 LOCAL_GL_TEXTURE_2D, attachInfo); 2144 webgl->Viewport(0, 0, mSize.width, mSize.height); 2145 mSharedContext->DisableScissor(); 2146 DeviceColor color = PremultiplyColor(GetClearPattern().mColor); 2147 webgl->ClearColor(color.b, color.g, color.r, color.a); 2148 webgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT); 2149 mSharedContext->ClearTarget(); 2150 mSharedContext->AddUntrackedTextureMemory(mTex); 2151 } 2152 return true; 2153 } 2154 2155 void DrawTargetWebgl::CopySurface(SourceSurface* aSurface, 2156 const IntRect& aSourceRect, 2157 const IntPoint& aDestination) { 2158 // Intersect the source and destination rectangles with the viewport bounds. 2159 IntRect destRect = 2160 IntRect(aDestination, aSourceRect.Size()).SafeIntersect(GetRect()); 2161 IntRect srcRect = destRect - aDestination + aSourceRect.TopLeft(); 2162 if (srcRect.IsEmpty()) { 2163 return; 2164 } 2165 2166 if (mSkiaValid) { 2167 if (mSkiaLayer) { 2168 if (destRect.Contains(GetRect())) { 2169 // If the the destination would override the entire layer, discard the 2170 // layer. 2171 mSkiaLayer = false; 2172 } else if (!IsOpaque(aSurface->GetFormat())) { 2173 // If the surface is not opaque, copying it into the layer results in 2174 // unintended blending rather than a copy to the destination. 2175 FlattenSkia(); 2176 } 2177 } else { 2178 // If there is no layer, copying is safe. 2179 MarkSkiaChanged(); 2180 } 2181 mSkia->CopySurface(aSurface, srcRect, destRect.TopLeft()); 2182 return; 2183 } 2184 2185 IntRect samplingRect; 2186 if (!mSharedContext->IsCompatibleSurface(aSurface)) { 2187 // If this data surface completely overwrites the framebuffer, then just 2188 // copy it to the Skia target. 2189 if (destRect.Contains(GetRect())) { 2190 MarkSkiaChanged(true); 2191 mSkia->DetachAllSnapshots(); 2192 mSkiaNoClip->CopySurface(aSurface, srcRect, destRect.TopLeft()); 2193 return; 2194 } 2195 2196 // CopySurface usually only samples a surface once, so don't cache the 2197 // entire surface as it is unlikely to be reused. Limit it to the used 2198 // source rectangle instead. 2199 IntRect surfaceRect = aSurface->GetRect(); 2200 if (!srcRect.IsEqualEdges(surfaceRect)) { 2201 samplingRect = srcRect.SafeIntersect(surfaceRect) - surfaceRect.TopLeft(); 2202 } 2203 } 2204 2205 Matrix matrix = Matrix::Translation(destRect.TopLeft() - srcRect.TopLeft()); 2206 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, matrix, 2207 SamplingFilter::POINT, samplingRect); 2208 DrawRect(Rect(destRect), pattern, DrawOptions(1.0f, CompositionOp::OP_SOURCE), 2209 Nothing(), nullptr, false, false); 2210 } 2211 2212 void DrawTargetWebgl::PushClip(const Path* aPath) { 2213 if (aPath && aPath->GetBackendType() == BackendType::SKIA) { 2214 // Detect if the path is really just a rect to simplify caching. 2215 if (Maybe<Rect> rect = aPath->AsRect()) { 2216 PushClipRect(*rect); 2217 return; 2218 } 2219 } 2220 2221 mClipChanged = true; 2222 mRefreshClipState = true; 2223 mSkia->PushClip(aPath); 2224 2225 mClipStack.push_back({GetTransform(), Rect(), aPath}); 2226 } 2227 2228 void DrawTargetWebgl::PushClipRect(const Rect& aRect) { 2229 mClipChanged = true; 2230 mRefreshClipState = true; 2231 mSkia->PushClipRect(aRect); 2232 2233 mClipStack.push_back({GetTransform(), aRect, nullptr}); 2234 } 2235 2236 void DrawTargetWebgl::PushDeviceSpaceClipRects(const IntRect* aRects, 2237 uint32_t aCount) { 2238 mClipChanged = true; 2239 mRefreshClipState = true; 2240 mSkia->PushDeviceSpaceClipRects(aRects, aCount); 2241 2242 for (uint32_t i = 0; i < aCount; i++) { 2243 mClipStack.push_back({Matrix(), Rect(aRects[i]), nullptr}); 2244 } 2245 } 2246 2247 void DrawTargetWebgl::PopClip() { 2248 if (mClipStack.empty()) { 2249 return; 2250 } 2251 2252 mClipChanged = true; 2253 mRefreshClipState = true; 2254 mSkia->PopClip(); 2255 2256 mClipStack.pop_back(); 2257 } 2258 2259 bool DrawTargetWebgl::RemoveAllClips() { 2260 if (mClipStack.empty()) { 2261 return true; 2262 } 2263 if (!mSkia->RemoveAllClips()) { 2264 return false; 2265 } 2266 mClipChanged = true; 2267 mRefreshClipState = true; 2268 mClipStack.clear(); 2269 return true; 2270 } 2271 2272 bool DrawTargetWebgl::CopyToFallback(DrawTarget* aDT) { 2273 aDT->RemoveAllClips(); 2274 for (auto& clipStack : mClipStack) { 2275 aDT->SetTransform(clipStack.mTransform); 2276 if (clipStack.mPath) { 2277 aDT->PushClip(clipStack.mPath); 2278 } else { 2279 aDT->PushClipRect(clipStack.mRect); 2280 } 2281 } 2282 aDT->SetTransform(GetTransform()); 2283 2284 // An existing data snapshot is required for fallback, as we have to avoid 2285 // trying to touch the WebGL context, which is assumed to be invalid and not 2286 // suitable for readback. 2287 if (HasDataSnapshot()) { 2288 if (RefPtr<SourceSurface> snapshot = Snapshot()) { 2289 aDT->CopySurface(snapshot, snapshot->GetRect(), gfx::IntPoint(0, 0)); 2290 return true; 2291 } 2292 } 2293 return false; 2294 } 2295 2296 enum class SupportsDrawOptionsStatus { No, UnboundedBlend, Yes }; 2297 2298 // Whether a given composition operator can be mapped to a WebGL blend mode. 2299 static inline SupportsDrawOptionsStatus SupportsDrawOptions( 2300 const DrawOptions& aOptions) { 2301 switch (aOptions.mCompositionOp) { 2302 case CompositionOp::OP_OVER: 2303 case CompositionOp::OP_DEST_OVER: 2304 case CompositionOp::OP_ADD: 2305 case CompositionOp::OP_DEST_OUT: 2306 case CompositionOp::OP_ATOP: 2307 case CompositionOp::OP_SOURCE: 2308 case CompositionOp::OP_CLEAR: 2309 case CompositionOp::OP_MULTIPLY: 2310 case CompositionOp::OP_SCREEN: 2311 return SupportsDrawOptionsStatus::Yes; 2312 case CompositionOp::OP_IN: 2313 case CompositionOp::OP_OUT: 2314 case CompositionOp::OP_DEST_IN: 2315 case CompositionOp::OP_DEST_ATOP: 2316 return SupportsDrawOptionsStatus::UnboundedBlend; 2317 default: 2318 return SupportsDrawOptionsStatus::No; 2319 } 2320 } 2321 2322 bool DrawTargetWebgl::SupportsDrawOptions(const DrawOptions& aOptions, 2323 const Rect& aRect) { 2324 switch (mozilla::gfx::SupportsDrawOptions(aOptions)) { 2325 case SupportsDrawOptionsStatus::Yes: 2326 return true; 2327 case SupportsDrawOptionsStatus::UnboundedBlend: 2328 if (aRect.IsEmpty()) { 2329 return false; 2330 } 2331 if (Maybe<IntRect> clip = mSkia->GetDeviceClipRect(false)) { 2332 if (!clip->IsEmpty() && clip->Contains(GetRect())) { 2333 clip = Some(GetRect()); 2334 } 2335 Rect clipF(*clip); 2336 if (aRect.Contains(clipF) || aRect.WithinEpsilonOf(clipF, 1e-3f)) { 2337 return true; 2338 } 2339 return false; 2340 } 2341 return false; 2342 default: 2343 return false; 2344 } 2345 } 2346 2347 // Whether the composition operator requires multiple blend stages to 2348 // approximate with WebGL blend modes. 2349 inline uint8_t SharedContextWebgl::RequiresMultiStageBlend( 2350 const DrawOptions& aOptions, DrawTargetWebgl* aDT) { 2351 switch (aOptions.mCompositionOp) { 2352 case CompositionOp::OP_MULTIPLY: 2353 return !IsOpaque(aDT ? aDT->GetFormat() 2354 : (mTargetHandle ? mTargetHandle->GetFormat() 2355 : mCurrentTarget->GetFormat())) 2356 ? 2 2357 : 0; 2358 default: 2359 return 0; 2360 } 2361 } 2362 2363 static inline bool SupportsExtendMode(const SurfacePattern& aPattern) { 2364 switch (aPattern.mExtendMode) { 2365 case ExtendMode::CLAMP: 2366 return true; 2367 case ExtendMode::REPEAT: 2368 case ExtendMode::REPEAT_X: 2369 case ExtendMode::REPEAT_Y: 2370 if ((!aPattern.mSurface || 2371 aPattern.mSurface->GetUnderlyingType() == SurfaceType::WEBGL) && 2372 !aPattern.mSamplingRect.IsEmpty()) { 2373 return false; 2374 } 2375 return true; 2376 default: 2377 return false; 2378 } 2379 } 2380 2381 // Whether a pattern can be mapped to an available WebGL shader. 2382 bool SharedContextWebgl::SupportsPattern(const Pattern& aPattern) { 2383 switch (aPattern.GetType()) { 2384 case PatternType::COLOR: 2385 return true; 2386 case PatternType::SURFACE: { 2387 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern); 2388 if (!SupportsExtendMode(surfacePattern)) { 2389 return false; 2390 } 2391 if (surfacePattern.mSurface) { 2392 // If the surface is already uploaded to a texture, then just use it. 2393 if (IsCompatibleSurface(surfacePattern.mSurface)) { 2394 return true; 2395 } 2396 2397 IntSize size = surfacePattern.mSurface->GetSize(); 2398 // The maximum size a surface can be before triggering a fallback to 2399 // software. Bound the maximum surface size by the actual texture size 2400 // limit. 2401 int32_t maxSize = int32_t( 2402 std::min(StaticPrefs::gfx_canvas_accelerated_max_surface_size(), 2403 mMaxTextureSize)); 2404 // Check if either of the surface dimensions or the sampling rect, 2405 // if supplied, exceed the maximum. 2406 if (std::max(size.width, size.height) > maxSize && 2407 (surfacePattern.mSamplingRect.IsEmpty() || 2408 std::max(surfacePattern.mSamplingRect.width, 2409 surfacePattern.mSamplingRect.height) > maxSize)) { 2410 return false; 2411 } 2412 } 2413 return true; 2414 } 2415 default: 2416 // Patterns other than colors and surfaces are currently not accelerated. 2417 return false; 2418 } 2419 } 2420 2421 bool DrawTargetWebgl::DrawRect(const Rect& aRect, const Pattern& aPattern, 2422 const DrawOptions& aOptions, 2423 Maybe<DeviceColor> aMaskColor, 2424 RefPtr<TextureHandle>* aHandle, 2425 bool aTransformed, bool aClipped, 2426 bool aAccelOnly, bool aForceUpdate, 2427 const StrokeOptions* aStrokeOptions) { 2428 // If there is nothing to draw, then don't draw... 2429 if (aRect.IsEmpty() || mSkia->IsClipEmpty()) { 2430 return true; 2431 } 2432 2433 // If we're already drawing directly to the WebGL context, then we want to 2434 // continue to do so. However, if we're drawing into a Skia layer over the 2435 // WebGL context, then we need to be careful to avoid repeatedly clearing 2436 // and flushing the layer if we hit a drawing request that can be accelerated 2437 // in between layered drawing requests, as clearing and flushing the layer 2438 // can be significantly expensive when repeated. So when a Skia layer is 2439 // active, if it is possible to continue drawing into the layer, then don't 2440 // accelerate the drawing request. 2441 if (mWebglValid || (mSkiaLayer && !mLayerDepth && 2442 (aAccelOnly || !SupportsLayering(aOptions)))) { 2443 // If we get here, either the WebGL context is being directly drawn to 2444 // or we are going to flush the Skia layer to it before doing so. The shared 2445 // context still needs to be claimed and prepared for drawing. If this 2446 // fails, we just fall back to drawing with Skia below. 2447 if (SupportsDrawOptions(aOptions, aRect) && PrepareContext(aClipped)) { 2448 // The shared context is claimed and the framebuffer is now valid, so try 2449 // accelerated drawing. 2450 return mSharedContext->DrawRectAccel( 2451 aRect, aPattern, aOptions, aMaskColor, aHandle, aTransformed, 2452 aClipped, aAccelOnly, aForceUpdate, aStrokeOptions); 2453 } 2454 } 2455 2456 // Either there is no valid WebGL target to draw into, or we failed to prepare 2457 // it for drawing. The only thing we can do at this point is fall back to 2458 // drawing with Skia. If the request explicitly requires accelerated drawing, 2459 // then draw nothing before returning failure. 2460 if (!aAccelOnly) { 2461 DrawRectFallback(aRect, aPattern, aOptions, aMaskColor, aTransformed, 2462 aClipped, aStrokeOptions); 2463 } 2464 return false; 2465 } 2466 2467 void DrawTargetWebgl::DrawRectFallback(const Rect& aRect, 2468 const Pattern& aPattern, 2469 const DrawOptions& aOptions, 2470 Maybe<DeviceColor> aMaskColor, 2471 bool aTransformed, bool aClipped, 2472 const StrokeOptions* aStrokeOptions) { 2473 // Invalidate the WebGL target and prepare the Skia target for drawing. 2474 MarkSkiaChanged(aOptions); 2475 2476 if (aTransformed) { 2477 // If transforms are requested, then just translate back to FillRect. 2478 if (aMaskColor) { 2479 mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions); 2480 } else if (aStrokeOptions) { 2481 mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions); 2482 } else { 2483 mSkia->FillRect(aRect, aPattern, aOptions); 2484 } 2485 } else if (aClipped) { 2486 // If no transform was requested but clipping is still required, then 2487 // temporarily reset the transform before translating to FillRect. 2488 mSkia->SetTransform(Matrix()); 2489 if (aMaskColor) { 2490 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern); 2491 if (surfacePattern.mSamplingRect.IsEmpty()) { 2492 mSkia->MaskSurface(ColorPattern(*aMaskColor), surfacePattern.mSurface, 2493 aRect.TopLeft(), aOptions); 2494 } else { 2495 mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions); 2496 } 2497 } else if (aStrokeOptions) { 2498 mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions); 2499 } else { 2500 mSkia->FillRect(aRect, aPattern, aOptions); 2501 } 2502 mSkia->SetTransform(mTransform); 2503 } else if (aPattern.GetType() == PatternType::SURFACE) { 2504 // No transform nor clipping was requested, so it is essentially just a 2505 // copy. 2506 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern); 2507 IntRect destRect = RoundedOut(aRect); 2508 IntRect srcRect = 2509 destRect - IntPoint::Round(surfacePattern.mMatrix.GetTranslation()); 2510 mSkia->CopySurface(surfacePattern.mSurface, srcRect, destRect.TopLeft()); 2511 } else { 2512 MOZ_ASSERT(false); 2513 } 2514 } 2515 2516 inline already_AddRefed<WebGLTexture> SharedContextWebgl::GetCompatibleSnapshot( 2517 SourceSurface* aSurface, RefPtr<TextureHandle>* aHandle, 2518 bool aCheckTarget) const { 2519 if (aSurface->GetUnderlyingType() == SurfaceType::WEBGL) { 2520 RefPtr<SourceSurfaceWebgl> webglSurf = 2521 aSurface->GetUnderlyingSurface().downcast<SourceSurfaceWebgl>(); 2522 if (this == webglSurf->mSharedContext) { 2523 // If there is a snapshot copy in a texture handle, use that. 2524 if (webglSurf->mHandle) { 2525 if (aHandle) { 2526 *aHandle = webglSurf->mHandle; 2527 } 2528 return do_AddRef( 2529 webglSurf->mHandle->GetBackingTexture()->GetWebGLTexture()); 2530 } 2531 if (RefPtr<DrawTargetWebgl> webglDT = webglSurf->GetTarget()) { 2532 // If there is a copy-on-write reference to a target, use its backing 2533 // texture directly. This is only safe if the targets don't match, but 2534 // MarkChanged should ensure that any snapshots were copied into a 2535 // texture handle before we ever get here. 2536 if (!aCheckTarget || !IsCurrentTarget(webglDT)) { 2537 return do_AddRef(webglDT->mTex); 2538 } 2539 } 2540 } 2541 } 2542 return nullptr; 2543 } 2544 2545 inline bool SharedContextWebgl::IsCompatibleSurface( 2546 SourceSurface* aSurface) const { 2547 return bool(RefPtr<WebGLTexture>(GetCompatibleSnapshot(aSurface))); 2548 } 2549 2550 bool SharedContextWebgl::UploadSurface(DataSourceSurface* aData, 2551 SurfaceFormat aFormat, 2552 const IntRect& aSrcRect, 2553 const IntPoint& aDstOffset, bool aInit, 2554 bool aZero, 2555 const RefPtr<WebGLTexture>& aTex) { 2556 webgl::TexUnpackBlobDesc texDesc = {LOCAL_GL_TEXTURE_2D}; 2557 IntRect srcRect(aSrcRect); 2558 IntPoint dstOffset(aDstOffset); 2559 if (srcRect.IsEmpty()) { 2560 return true; 2561 } 2562 if (aData) { 2563 // If the source rect could not possibly overlap the surface, then it is 2564 // effectively empty with nothing to upload. 2565 srcRect = srcRect.SafeIntersect(IntRect(IntPoint(0, 0), aData->GetSize())); 2566 if (srcRect.IsEmpty()) { 2567 return true; 2568 } 2569 // If there is a non-empty rect remaining, then ensure the dest offset 2570 // reflects the change in source rect. 2571 dstOffset += srcRect.TopLeft() - aSrcRect.TopLeft(); 2572 2573 // Ensure source data matches the expected format size. 2574 int32_t bpp = BytesPerPixel(aFormat); 2575 if (bpp != BytesPerPixel(aData->GetFormat())) { 2576 return false; 2577 } 2578 2579 // The surface needs to be uploaded to its backing texture either to 2580 // initialize or update the texture handle contents. Map the data 2581 // contents of the surface so it can be read. 2582 DataSourceSurface::ScopedMap map(aData, DataSourceSurface::READ); 2583 if (!map.IsMapped()) { 2584 return false; 2585 } 2586 int32_t stride = map.GetStride(); 2587 // Get the data pointer range considering the sampling rect offset and 2588 // size. 2589 Span<const uint8_t> range( 2590 map.GetData() + srcRect.y * size_t(stride) + srcRect.x * bpp, 2591 std::max(srcRect.height - 1, 0) * size_t(stride) + srcRect.width * bpp); 2592 texDesc.cpuData = Some(range); 2593 // If the stride happens to be 4 byte aligned, assume that is the 2594 // desired alignment regardless of format (even A8). Otherwise, we 2595 // default to byte alignment. 2596 texDesc.unpacking.alignmentInTypeElems = stride % 4 ? 1 : 4; 2597 texDesc.unpacking.rowLength = stride / bpp; 2598 } else if (aZero) { 2599 // Create a PBO filled with zero data to initialize the texture data and 2600 // avoid slow initialization inside WebGL. 2601 if (srcRect.TopLeft() != IntPoint(0, 0)) { 2602 MOZ_ASSERT_UNREACHABLE("Invalid origin for texture initialization."); 2603 return false; 2604 } 2605 int32_t stride = GetAlignedStride<4>(srcRect.width, BytesPerPixel(aFormat)); 2606 if (stride <= 0) { 2607 MOZ_ASSERT_UNREACHABLE("Invalid stride for texture initialization."); 2608 return false; 2609 } 2610 size_t size = size_t(stride) * srcRect.height; 2611 if (!mZeroBuffer || size > mZeroSize) { 2612 ClearZeroBuffer(); 2613 mZeroBuffer = mWebgl->CreateBuffer(); 2614 mZeroSize = size; 2615 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mZeroBuffer); 2616 // WebGL will zero initialize the empty buffer, so we don't send zero data 2617 // explicitly. 2618 mWebgl->BufferData(LOCAL_GL_PIXEL_UNPACK_BUFFER, size, nullptr, 2619 LOCAL_GL_STATIC_DRAW); 2620 AddUntrackedTextureMemory(mZeroBuffer); 2621 } else { 2622 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mZeroBuffer); 2623 } 2624 texDesc.pboOffset = Some(0); 2625 } 2626 texDesc.size = uvec3(uint32_t(srcRect.width), uint32_t(srcRect.height), 1); 2627 // Upload as RGBA8 to avoid swizzling during upload. Surfaces provide 2628 // data as BGRA, but we manually swizzle that in the shader. An A8 2629 // surface will be stored as an R8 texture that will also be swizzled 2630 // in the shader. 2631 GLenum intFormat = 2632 aFormat == SurfaceFormat::A8 ? LOCAL_GL_R8 : LOCAL_GL_RGBA8; 2633 GLenum extFormat = 2634 aFormat == SurfaceFormat::A8 ? LOCAL_GL_RED : LOCAL_GL_RGBA; 2635 webgl::PackingInfo texPI = {extFormat, LOCAL_GL_UNSIGNED_BYTE}; 2636 // Do the (partial) upload for the shared or standalone texture. 2637 if (aTex) { 2638 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, aTex); 2639 } 2640 mWebgl->TexImage(0, aInit ? intFormat : 0, 2641 {uint32_t(dstOffset.x), uint32_t(dstOffset.y), 0}, texPI, 2642 texDesc); 2643 if (aTex) { 2644 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mLastTexture); 2645 } 2646 if (!aData && aZero) { 2647 mWebgl->BindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0); 2648 } 2649 return true; 2650 } 2651 2652 void SharedContextWebgl::UploadSurfaceToHandle( 2653 const RefPtr<DataSourceSurface>& aData, const IntPoint& aSrcOffset, 2654 const RefPtr<TextureHandle>& aHandle) { 2655 BackingTexture* backing = aHandle->GetBackingTexture(); 2656 RefPtr<WebGLTexture> tex = backing->GetWebGLTexture(); 2657 if (mLastTexture != tex) { 2658 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex); 2659 mLastTexture = tex; 2660 } 2661 if (!backing->IsInitialized()) { 2662 backing->MarkInitialized(); 2663 InitTexParameters(tex); 2664 if (aHandle->GetSize() != backing->GetSize()) { 2665 UploadSurface(nullptr, backing->GetFormat(), 2666 IntRect(IntPoint(), backing->GetSize()), IntPoint(), true, 2667 true); 2668 } 2669 } 2670 UploadSurface( 2671 aData, aHandle->GetFormat(), IntRect(aSrcOffset, aHandle->GetSize()), 2672 aHandle->GetBounds().TopLeft(), aHandle->GetSize() == backing->GetSize()); 2673 // Signal that we had to upload new data to the texture cache. 2674 mCurrentTarget->mProfile.OnCacheMiss(); 2675 } 2676 2677 void SharedContextWebgl::BindAndInitRenderTex(const RefPtr<WebGLTexture>& aTex, 2678 SurfaceFormat aFormat, 2679 const IntSize& aSize) { 2680 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, aTex); 2681 mWebgl->TexStorage( 2682 LOCAL_GL_TEXTURE_2D, 1, 2683 aFormat == SurfaceFormat::A8 ? LOCAL_GL_R8 : LOCAL_GL_RGBA8, 2684 {uint32_t(aSize.width), uint32_t(aSize.height), 1}); 2685 InitTexParameters(aTex); 2686 ClearLastTexture(); 2687 } 2688 2689 void SharedContextWebgl::InitRenderTex(BackingTexture* aBacking) { 2690 // Initialize the backing texture if necessary. 2691 if (!aBacking->IsInitialized()) { 2692 // If the backing texture is uninitialized, it needs its sampling parameters 2693 // set for later use. 2694 BindAndInitRenderTex(aBacking->GetWebGLTexture(), aBacking->GetFormat(), 2695 aBacking->GetSize()); 2696 } 2697 } 2698 2699 void SharedContextWebgl::ClearRenderTex(BackingTexture* aBacking) { 2700 if (!aBacking->IsInitialized()) { 2701 aBacking->MarkInitialized(); 2702 // WebGL implicitly clears the backing texture the first time it is used. 2703 } else { 2704 // Ensure the mask background is clear. 2705 mWebgl->ClearColor(0.0f, 0.0f, 0.0f, 0.0f); 2706 mWebgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT); 2707 } 2708 } 2709 2710 void SharedContextWebgl::BindScratchFramebuffer(TextureHandle* aHandle, 2711 bool aInit, 2712 const IntSize& aViewportSize) { 2713 BackingTexture* backing = aHandle->GetBackingTexture(); 2714 if (aInit) { 2715 InitRenderTex(backing); 2716 } 2717 2718 // Set up a scratch framebuffer to render to the appropriate sub-texture of 2719 // the backing texture. 2720 if (!mScratchFramebuffer) { 2721 mScratchFramebuffer = mWebgl->CreateFramebuffer(); 2722 } 2723 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mScratchFramebuffer); 2724 webgl::FbAttachInfo attachInfo; 2725 attachInfo.tex = backing->GetWebGLTexture(); 2726 mWebgl->FramebufferAttach(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, 2727 LOCAL_GL_TEXTURE_2D, attachInfo); 2728 IntRect bounds = aHandle->GetBounds(); 2729 if (!aViewportSize.IsEmpty()) { 2730 bounds.SizeTo(Min(bounds.Size(), aViewportSize)); 2731 } 2732 mWebgl->Viewport(bounds.x, bounds.y, bounds.width, bounds.height); 2733 2734 if (aInit) { 2735 EnableScissor(bounds, true); 2736 ClearRenderTex(backing); 2737 } 2738 } 2739 2740 // Allocate a new texture handle backed by either a standalone texture or as a 2741 // sub-texture of a larger shared texture. 2742 already_AddRefed<TextureHandle> SharedContextWebgl::AllocateTextureHandle( 2743 SurfaceFormat aFormat, const IntSize& aSize, bool aAllowShared, 2744 bool aRenderable, const WebGLTexture* aAvoid) { 2745 RefPtr<TextureHandle> handle; 2746 // Calculate the bytes that would be used by this texture handle, and prune 2747 // enough other textures to ensure we have that much usable texture space 2748 // available to allocate. 2749 size_t usedBytes = BackingTexture::UsedBytes(aFormat, aSize); 2750 PruneTextureMemory(usedBytes, false); 2751 // The requested page size for shared textures. 2752 int32_t pageSize = int32_t(std::min( 2753 StaticPrefs::gfx_canvas_accelerated_shared_page_size(), mMaxTextureSize)); 2754 if (aAllowShared && std::max(aSize.width, aSize.height) <= pageSize / 2) { 2755 // Ensure that the surface is no bigger than a quadrant of a shared texture 2756 // page. If so, try to allocate it to a shared texture. Look for any 2757 // existing shared texture page with a matching format and allocate 2758 // from that if possible. 2759 for (auto& shared : mSharedTextures) { 2760 if (shared->GetFormat() == aFormat && 2761 shared->IsRenderable() == aRenderable && 2762 shared->GetWebGLTexture() != aAvoid) { 2763 bool wasEmpty = !shared->HasAllocatedHandles(); 2764 handle = shared->Allocate(aSize); 2765 if (handle) { 2766 if (wasEmpty) { 2767 // If the page was previously empty, then deduct it from the 2768 // empty memory reserves. 2769 mEmptyTextureMemory -= shared->UsedBytes(); 2770 } 2771 break; 2772 } 2773 } 2774 } 2775 // If we couldn't find an existing shared texture page with matching 2776 // format, then allocate a new page to put the request in. 2777 if (!handle) { 2778 if (RefPtr<WebGLTexture> tex = mWebgl->CreateTexture()) { 2779 RefPtr<SharedTexture> shared = 2780 new SharedTexture(IntSize(pageSize, pageSize), aFormat, tex); 2781 if (aRenderable) { 2782 shared->MarkRenderable(); 2783 } 2784 mSharedTextures.push_back(shared); 2785 AddTextureMemory(shared); 2786 handle = shared->Allocate(aSize); 2787 } 2788 } 2789 } else { 2790 // The surface wouldn't fit in a shared texture page, so we need to 2791 // allocate a standalone texture for it instead. 2792 if (RefPtr<WebGLTexture> tex = mWebgl->CreateTexture()) { 2793 RefPtr<StandaloneTexture> standalone = 2794 new StandaloneTexture(aSize, aFormat, tex); 2795 if (aRenderable) { 2796 standalone->MarkRenderable(); 2797 } 2798 mStandaloneTextures.push_back(standalone); 2799 AddTextureMemory(standalone); 2800 handle = standalone; 2801 } 2802 } 2803 2804 if (!handle) { 2805 return nullptr; 2806 } 2807 2808 // Insert the new texture handle into the front of the MRU list and 2809 // update used space for it. 2810 mTextureHandles.insertFront(handle); 2811 ++mNumTextureHandles; 2812 mUsedTextureMemory += handle->UsedBytes(); 2813 2814 return handle.forget(); 2815 } 2816 2817 static inline SamplingFilter GetSamplingFilter(const Pattern& aPattern) { 2818 return aPattern.GetType() == PatternType::SURFACE 2819 ? static_cast<const SurfacePattern&>(aPattern).mSamplingFilter 2820 : SamplingFilter::GOOD; 2821 } 2822 2823 static inline bool UseNearestFilter(const Pattern& aPattern) { 2824 return GetSamplingFilter(aPattern) == SamplingFilter::POINT; 2825 } 2826 2827 // Determine if the rectangle is still axis-aligned and pixel-aligned. 2828 static inline Maybe<IntRect> IsAlignedRect(bool aTransformed, 2829 const Matrix& aCurrentTransform, 2830 const Rect& aRect) { 2831 if (!aTransformed || aCurrentTransform.HasOnlyIntegerTranslation()) { 2832 auto intRect = RoundedToInt(aRect); 2833 if (aRect.WithinEpsilonOf(Rect(intRect), 1.0e-3f)) { 2834 if (aTransformed) { 2835 intRect += RoundedToInt(aCurrentTransform.GetTranslation()); 2836 } 2837 return Some(intRect); 2838 } 2839 } 2840 return Nothing(); 2841 } 2842 2843 Maybe<uint32_t> SharedContextWebgl::GetUniformLocation( 2844 const RefPtr<WebGLProgram>& aProg, const std::string& aName) const { 2845 if (!aProg || !aProg->LinkInfo()) { 2846 return Nothing(); 2847 } 2848 2849 for (const auto& activeUniform : aProg->LinkInfo()->active.activeUniforms) { 2850 if (activeUniform.block_index != -1) continue; 2851 2852 auto locName = activeUniform.name; 2853 const auto indexed = webgl::ParseIndexed(locName); 2854 if (indexed) { 2855 locName = indexed->name; 2856 } 2857 2858 const auto baseLength = locName.size(); 2859 for (const auto& pair : activeUniform.locByIndex) { 2860 if (indexed) { 2861 locName.erase(baseLength); // Erase previous "[N]". 2862 locName += '['; 2863 locName += std::to_string(pair.first); 2864 locName += ']'; 2865 } 2866 if (locName == aName || locName == aName + "[0]") { 2867 return Some(pair.second); 2868 } 2869 } 2870 } 2871 2872 return Nothing(); 2873 } 2874 2875 template <class T> 2876 struct IsUniformDataValT : std::false_type {}; 2877 template <> 2878 struct IsUniformDataValT<webgl::UniformDataVal> : std::true_type {}; 2879 template <> 2880 struct IsUniformDataValT<float> : std::true_type {}; 2881 template <> 2882 struct IsUniformDataValT<int32_t> : std::true_type {}; 2883 template <> 2884 struct IsUniformDataValT<uint32_t> : std::true_type {}; 2885 2886 template <typename T, typename = std::enable_if_t<IsUniformDataValT<T>::value>> 2887 inline Range<const webgl::UniformDataVal> AsUniformDataVal( 2888 const Range<const T>& data) { 2889 return {data.begin().template ReinterpretCast<const webgl::UniformDataVal>(), 2890 data.end().template ReinterpretCast<const webgl::UniformDataVal>()}; 2891 } 2892 2893 template <class T, size_t N> 2894 inline void SharedContextWebgl::UniformData(GLenum aFuncElemType, 2895 const Maybe<uint32_t>& aLoc, 2896 const Array<T, N>& aData) { 2897 // We currently always pass false for transpose. If in the future we need 2898 // support for transpose then caching needs to take that in to account. 2899 mWebgl->UniformData(*aLoc, false, 2900 AsUniformDataVal(Range<const T>(Span<const T>(aData)))); 2901 } 2902 2903 template <class T, size_t N> 2904 void SharedContextWebgl::MaybeUniformData(GLenum aFuncElemType, 2905 const Maybe<uint32_t>& aLoc, 2906 const Array<T, N>& aData, 2907 Maybe<Array<T, N>>& aCached) { 2908 if (aCached.isNothing() || !(*aCached == aData)) { 2909 aCached = Some(aData); 2910 UniformData(aFuncElemType, aLoc, aData); 2911 } 2912 } 2913 2914 inline void SharedContextWebgl::DrawQuad() { 2915 mWebgl->DrawArraysInstanced(LOCAL_GL_TRIANGLE_FAN, 0, 4, 1); 2916 } 2917 2918 void SharedContextWebgl::DrawTriangles(const PathVertexRange& aRange) { 2919 mWebgl->DrawArraysInstanced(LOCAL_GL_TRIANGLES, GLint(aRange.mOffset), 2920 GLsizei(aRange.mLength), 1); 2921 } 2922 2923 // Common rectangle and pattern drawing function shared by many DrawTarget 2924 // commands. If aMaskColor is specified, the provided surface pattern will be 2925 // treated as a mask. If aHandle is specified, then the surface pattern's 2926 // texture will be cached in the supplied handle, as opposed to using the 2927 // surface's user data. If aTransformed or aClipped are false, then transforms 2928 // and/or clipping will be disabled. If aAccelOnly is specified, then this 2929 // function will return before it would have otherwise drawn without 2930 // acceleration. If aForceUpdate is specified, then the provided texture handle 2931 // will be respecified with the provided surface. 2932 bool SharedContextWebgl::DrawRectAccel( 2933 const Rect& aRect, const Pattern& aPattern, const DrawOptions& aOptions, 2934 Maybe<DeviceColor> aMaskColor, RefPtr<TextureHandle>* aHandle, 2935 bool aTransformed, bool aClipped, bool aAccelOnly, bool aForceUpdate, 2936 const StrokeOptions* aStrokeOptions, const PathVertexRange* aVertexRange, 2937 const Matrix* aRectXform, uint8_t aBlendStage) { 2938 // If the rect or clip rect is empty, then there is nothing to draw. 2939 if (aRect.IsEmpty() || mClipRect.IsEmpty()) { 2940 return true; 2941 } 2942 2943 // Check if the drawing options and the pattern support acceleration. Also 2944 // ensure the framebuffer is prepared for drawing. If not, fall back to using 2945 // the Skia target. When we need to forcefully update a texture, we must be 2946 // careful to override any pattern limits, as the caller ensures the pattern 2947 // is otherwise a supported type. 2948 if (SupportsDrawOptions(aOptions) == SupportsDrawOptionsStatus::No || 2949 (!aForceUpdate && !SupportsPattern(aPattern)) || aStrokeOptions || 2950 (!mTargetHandle && !mCurrentTarget->MarkChanged())) { 2951 // If only accelerated drawing was requested, bail out without software 2952 // drawing fallback. 2953 if (!aAccelOnly) { 2954 MOZ_ASSERT(!aVertexRange); 2955 mCurrentTarget->DrawRectFallback(aRect, aPattern, aOptions, aMaskColor, 2956 aTransformed, aClipped, aStrokeOptions); 2957 } 2958 return false; 2959 } 2960 2961 if (!aBlendStage) { 2962 if (uint8_t numStages = RequiresMultiStageBlend(aOptions)) { 2963 for (uint8_t stage = 1; stage <= numStages; ++stage) { 2964 if (!DrawRectAccel(aRect, aPattern, aOptions, aMaskColor, aHandle, 2965 aTransformed, aClipped, aAccelOnly, aForceUpdate, 2966 aStrokeOptions, aVertexRange, aRectXform, stage)) { 2967 return false; 2968 } 2969 } 2970 return true; 2971 } 2972 } 2973 2974 const Matrix& currentTransform = mCurrentTarget->GetTransform(); 2975 // rectXform only applies to the rect, but should not apply to the pattern, 2976 // as it might inadvertently alter the pattern. 2977 Matrix rectXform = currentTransform; 2978 if (aRectXform) { 2979 rectXform.PreMultiply(*aRectXform); 2980 } 2981 2982 if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE && aClipped && 2983 (aVertexRange || 2984 !(aTransformed 2985 ? rectXform.PreservesAxisAlignedRectangles() && 2986 rectXform.TransformBounds(aRect).Contains(mClipAARect) 2987 : aRect.IsEqualEdges(Rect(mClipRect)) || 2988 aRect.Contains(mClipAARect)) || 2989 (aPattern.GetType() == PatternType::SURFACE && 2990 (HasClipMask() || !IsAlignedRect(false, Matrix(), mClipAARect))))) { 2991 // Clear outside the mask region for masks that are not bounded by clip. 2992 return DrawRectAccel(Rect(mClipRect), ColorPattern(DeviceColor(0, 0, 0, 0)), 2993 DrawOptions(1.0f, CompositionOp::OP_SOURCE, 2994 aOptions.mAntialiasMode), 2995 Nothing(), nullptr, false, aClipped, aAccelOnly) && 2996 DrawRectAccel(aRect, aPattern, 2997 DrawOptions(aOptions.mAlpha, CompositionOp::OP_ADD, 2998 aOptions.mAntialiasMode), 2999 aMaskColor, aHandle, aTransformed, aClipped, 3000 aAccelOnly, aForceUpdate, aStrokeOptions, aVertexRange, 3001 aRectXform); 3002 } 3003 if (aOptions.mCompositionOp == CompositionOp::OP_CLEAR && 3004 aPattern.GetType() == PatternType::SURFACE && !aMaskColor) { 3005 // If the surface being drawn with clear is not a mask, then its contents 3006 // needs to be ignored. Just use a color pattern instead. 3007 return DrawRectAccel(aRect, ColorPattern(DeviceColor(1, 1, 1, 1)), aOptions, 3008 Nothing(), aHandle, aTransformed, aClipped, aAccelOnly, 3009 aForceUpdate, aStrokeOptions, aVertexRange, 3010 aRectXform); 3011 } 3012 3013 // Set up the scissor test to reflect the clipping rectangle, if supplied. 3014 if (!mClipRect.Contains(IntRect(IntPoint(), mViewportSize))) { 3015 EnableScissor(mClipRect); 3016 } else { 3017 DisableScissor(); 3018 } 3019 3020 bool success = false; 3021 3022 // Now try to actually draw the pattern... 3023 switch (aPattern.GetType()) { 3024 case PatternType::COLOR: { 3025 if (!aVertexRange) { 3026 // Only an uncached draw if not using the vertex cache. 3027 mCurrentTarget->mProfile.OnUncachedDraw(); 3028 } 3029 DeviceColor color = PremultiplyColor( 3030 static_cast<const ColorPattern&>(aPattern).mColor, aOptions.mAlpha); 3031 if (((color.a == 1.0f && 3032 aOptions.mCompositionOp == CompositionOp::OP_OVER) || 3033 aOptions.mCompositionOp == CompositionOp::OP_SOURCE || 3034 aOptions.mCompositionOp == CompositionOp::OP_CLEAR) && 3035 !aStrokeOptions && !aVertexRange && !HasClipMask() && 3036 mClipAARect.IsEqualEdges(Rect(mClipRect))) { 3037 // Certain color patterns can be mapped to scissored clears. The 3038 // composition op must effectively overwrite the destination, and the 3039 // transform must map to an axis-aligned integer rectangle. 3040 if (Maybe<IntRect> intRect = 3041 IsAlignedRect(aTransformed, rectXform, aRect)) { 3042 // Only use a clear if the area is larger than a quarter or the 3043 // viewport. 3044 if (intRect->Area() >= 3045 (mViewportSize.width / 2) * (mViewportSize.height / 2)) { 3046 if (!intRect->Contains(mClipRect)) { 3047 EnableScissor(intRect->Intersect(mClipRect)); 3048 } 3049 if (aOptions.mCompositionOp == CompositionOp::OP_CLEAR) { 3050 color = 3051 PremultiplyColor(mCurrentTarget->GetClearPattern().mColor); 3052 } 3053 mWebgl->ClearColor(color.b, color.g, color.r, color.a); 3054 mWebgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT); 3055 success = true; 3056 break; 3057 } 3058 } 3059 } 3060 // Map the composition op to a WebGL blend mode, if possible. 3061 Maybe<DeviceColor> blendColor; 3062 if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE || 3063 aOptions.mCompositionOp == CompositionOp::OP_CLEAR) { 3064 // The source operator can support clipping and AA by emulating it with 3065 // the over op. Supply the color with blend state, and set the shader 3066 // color to white, to avoid needing dual-source blending. 3067 blendColor = Some(color); 3068 // Both source and clear operators should output a mask from the shader. 3069 color = DeviceColor(1, 1, 1, 1); 3070 } 3071 SetBlendState(aOptions.mCompositionOp, blendColor, aBlendStage); 3072 // Since it couldn't be mapped to a scissored clear, we need to use the 3073 // solid color shader with supplied transform. 3074 if (mLastProgram != mSolidProgram) { 3075 mWebgl->UseProgram(mSolidProgram); 3076 mLastProgram = mSolidProgram; 3077 } 3078 Array<float, 2> viewportData = {float(mViewportSize.width), 3079 float(mViewportSize.height)}; 3080 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramViewport, viewportData, 3081 mSolidProgramUniformState.mViewport); 3082 3083 // Generated paths provide their own AA as vertex alpha. 3084 Array<float, 1> aaData = {aVertexRange ? 0.0f : 1.0f}; 3085 MaybeUniformData(LOCAL_GL_FLOAT, mSolidProgramAA, aaData, 3086 mSolidProgramUniformState.mAA); 3087 3088 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel 3089 // boundary. 3090 Array<float, 4> clipData = {mClipAARect.x - 0.5f, mClipAARect.y - 0.5f, 3091 mClipAARect.XMost() + 0.5f, 3092 mClipAARect.YMost() + 0.5f}; 3093 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramClipBounds, clipData, 3094 mSolidProgramUniformState.mClipBounds); 3095 3096 Array<float, 4> colorData = {color.b, color.g, color.r, color.a}; 3097 Matrix xform(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y); 3098 if (aTransformed) { 3099 xform *= rectXform; 3100 } 3101 Array<float, 6> xformData = {xform._11, xform._12, xform._21, 3102 xform._22, xform._31, xform._32}; 3103 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramTransform, xformData, 3104 mSolidProgramUniformState.mTransform); 3105 3106 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramColor, colorData, 3107 mSolidProgramUniformState.mColor); 3108 3109 // Finally draw the colored rectangle. 3110 if (aVertexRange) { 3111 // If there's a vertex range, then we need to draw triangles within from 3112 // generated from a path stored in the path vertex buffer. 3113 DrawTriangles(*aVertexRange); 3114 } else { 3115 // Otherwise we're drawing a simple filled rectangle. 3116 DrawQuad(); 3117 } 3118 success = true; 3119 break; 3120 } 3121 case PatternType::SURFACE: { 3122 auto surfacePattern = static_cast<const SurfacePattern&>(aPattern); 3123 // If a texture handle was supplied, or if the surface already has an 3124 // assigned texture handle stashed in its used data, try to use it. 3125 RefPtr<SourceSurface> underlyingSurface = 3126 surfacePattern.mSurface 3127 ? surfacePattern.mSurface->GetUnderlyingSurface() 3128 : nullptr; 3129 RefPtr<TextureHandle> handle = 3130 aHandle 3131 ? aHandle->get() 3132 : (underlyingSurface 3133 ? static_cast<TextureHandle*>( 3134 underlyingSurface->GetUserData(&mTextureHandleKey)) 3135 : nullptr); 3136 IntSize texSize; 3137 IntPoint offset; 3138 SurfaceFormat format; 3139 // Check if the found handle is still valid and if its sampling rect 3140 // matches the requested sampling rect. 3141 if (handle && handle->IsValid() && 3142 (surfacePattern.mSamplingRect.IsEmpty() || 3143 handle->GetSamplingRect().IsEqualEdges( 3144 surfacePattern.mSamplingRect)) && 3145 (surfacePattern.mExtendMode == ExtendMode::CLAMP || 3146 handle->GetType() == TextureHandle::STANDALONE)) { 3147 texSize = handle->GetSize(); 3148 format = handle->GetFormat(); 3149 offset = handle->GetSamplingOffset(); 3150 } else { 3151 // Otherwise, there is no handle that can be used yet, so extract 3152 // information from the surface pattern. 3153 handle = nullptr; 3154 if (!underlyingSurface) { 3155 // If there was no actual surface supplied, then we tried to draw 3156 // using a texture handle, but the texture handle wasn't valid. 3157 break; 3158 } 3159 texSize = underlyingSurface->GetSize(); 3160 format = underlyingSurface->GetFormat(); 3161 if (!surfacePattern.mSamplingRect.IsEmpty()) { 3162 texSize = surfacePattern.mSamplingRect.Size(); 3163 offset = surfacePattern.mSamplingRect.TopLeft(); 3164 } 3165 } 3166 3167 // We need to be able to transform from local space into texture space. 3168 Matrix invMatrix = surfacePattern.mMatrix; 3169 // Determine if the requested surface itself is offset from the underlying 3170 // surface. 3171 if (surfacePattern.mSurface) { 3172 invMatrix.PreTranslate(surfacePattern.mSurface->GetRect().TopLeft()); 3173 } 3174 // If drawing a pre-transformed vertex range, then we need to ensure the 3175 // user-space pattern is still transformed to screen-space. 3176 if (aVertexRange && !aTransformed) { 3177 invMatrix *= currentTransform; 3178 } 3179 if (!invMatrix.Invert()) { 3180 break; 3181 } 3182 if (aRectXform) { 3183 // If there is aRectXform, it must be applied to the source rectangle to 3184 // generate the proper input coordinates for the inverse pattern matrix. 3185 invMatrix.PreMultiply(*aRectXform); 3186 } 3187 3188 RefPtr<WebGLTexture> tex; 3189 IntRect bounds; 3190 IntSize backingSize; 3191 if (handle) { 3192 if (aForceUpdate) { 3193 RefPtr<DataSourceSurface> data = underlyingSurface->GetDataSurface(); 3194 if (!data) { 3195 break; 3196 } 3197 UploadSurfaceToHandle(data, offset, handle); 3198 // The size of the texture may change if we update contents. 3199 mUsedTextureMemory -= handle->UsedBytes(); 3200 handle->UpdateSize(texSize); 3201 mUsedTextureMemory += handle->UsedBytes(); 3202 handle->SetSamplingOffset(surfacePattern.mSamplingRect.TopLeft()); 3203 } else { 3204 // Count reusing a snapshot texture (no readback) as a cache hit. 3205 mCurrentTarget->mProfile.OnCacheHit(); 3206 } 3207 // If using an existing handle, move it to the front of the MRU list. 3208 handle->remove(); 3209 mTextureHandles.insertFront(handle); 3210 } else if ((tex = GetCompatibleSnapshot(underlyingSurface, &handle))) { 3211 backingSize = underlyingSurface->GetSize(); 3212 bounds = IntRect(offset, texSize); 3213 // Count reusing a snapshot texture (no readback) as a cache hit. 3214 mCurrentTarget->mProfile.OnCacheHit(); 3215 if (aHandle) { 3216 *aHandle = handle; 3217 } 3218 } else { 3219 // If we get here, we need a data surface for a texture upload. 3220 RefPtr<DataSourceSurface> data = underlyingSurface->GetDataSurface(); 3221 if (!data) { 3222 break; 3223 } 3224 // There is no existing handle. Try to allocate a new one. If the 3225 // surface size may change via a forced update, then don't allocate 3226 // from a shared texture page. 3227 handle = AllocateTextureHandle( 3228 format, texSize, 3229 !aForceUpdate && surfacePattern.mExtendMode == ExtendMode::CLAMP); 3230 if (!handle) { 3231 MOZ_ASSERT(false); 3232 break; 3233 } 3234 UploadSurfaceToHandle(data, offset, handle); 3235 // Link the handle to the surface's user data. 3236 handle->SetSamplingOffset(surfacePattern.mSamplingRect.TopLeft()); 3237 if (aHandle) { 3238 *aHandle = handle; 3239 } else { 3240 handle->SetSurface(underlyingSurface); 3241 underlyingSurface->AddUserData(&mTextureHandleKey, handle.get(), 3242 nullptr); 3243 } 3244 } 3245 if (handle) { 3246 BackingTexture* backing = handle->GetBackingTexture(); 3247 if (!tex) { 3248 tex = backing->GetWebGLTexture(); 3249 } 3250 bounds = bounds.IsEmpty() ? handle->GetBounds() 3251 : handle->GetBounds().SafeIntersect( 3252 bounds + handle->GetBounds().TopLeft()); 3253 backingSize = backing->GetSize(); 3254 } 3255 3256 // Map the composition op to a WebGL blend mode, if possible. If there is 3257 // a mask color and a texture with multiple channels, assume subpixel 3258 // blending. If we encounter the source op here, then assume the surface 3259 // is opaque (non-opaque is handled above) and emulate it with over. 3260 SetBlendState(aOptions.mCompositionOp, 3261 format != SurfaceFormat::A8 ? aMaskColor : Nothing(), 3262 aBlendStage); 3263 // Switch to the image shader and set up relevant transforms. 3264 if (mLastProgram != mImageProgram) { 3265 mWebgl->UseProgram(mImageProgram); 3266 mLastProgram = mImageProgram; 3267 } 3268 3269 Array<float, 2> viewportData = {float(mViewportSize.width), 3270 float(mViewportSize.height)}; 3271 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mImageProgramViewport, viewportData, 3272 mImageProgramUniformState.mViewport); 3273 3274 // AA is not supported for OP_SOURCE. Generated paths provide their own 3275 // AA as vertex alpha. 3276 Array<float, 1> aaData = { 3277 mLastCompositionOp == CompositionOp::OP_SOURCE || aVertexRange 3278 ? 0.0f 3279 : 1.0f}; 3280 MaybeUniformData(LOCAL_GL_FLOAT, mImageProgramAA, aaData, 3281 mImageProgramUniformState.mAA); 3282 3283 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel 3284 // boundary. 3285 Array<float, 4> clipData = {mClipAARect.x - 0.5f, mClipAARect.y - 0.5f, 3286 mClipAARect.XMost() + 0.5f, 3287 mClipAARect.YMost() + 0.5f}; 3288 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mImageProgramClipBounds, clipData, 3289 mImageProgramUniformState.mClipBounds); 3290 3291 DeviceColor color = 3292 mLastCompositionOp == CompositionOp::OP_CLEAR 3293 ? DeviceColor(1, 1, 1, 1) 3294 : PremultiplyColor( 3295 aMaskColor && format != SurfaceFormat::A8 3296 ? DeviceColor::Mask(1.0f, aMaskColor->a) 3297 : aMaskColor.valueOr(DeviceColor(1, 1, 1, 1)), 3298 aOptions.mAlpha); 3299 Array<float, 4> colorData = {color.b, color.g, color.r, color.a}; 3300 Array<float, 1> swizzleData = {format == SurfaceFormat::A8 ? 1.0f : 0.0f}; 3301 Matrix xform(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y); 3302 if (aTransformed) { 3303 xform *= rectXform; 3304 } 3305 Array<float, 6> xformData = {xform._11, xform._12, xform._21, 3306 xform._22, xform._31, xform._32}; 3307 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mImageProgramTransform, xformData, 3308 mImageProgramUniformState.mTransform); 3309 3310 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mImageProgramColor, colorData, 3311 mImageProgramUniformState.mColor); 3312 3313 MaybeUniformData(LOCAL_GL_FLOAT, mImageProgramSwizzle, swizzleData, 3314 mImageProgramUniformState.mSwizzle); 3315 3316 // Start binding the WebGL state for the texture. 3317 if (mLastTexture != tex) { 3318 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex); 3319 mLastTexture = tex; 3320 } 3321 3322 // Set up the texture coordinate matrix to map from the input rectangle to 3323 // the backing texture subrect. 3324 Size backingSizeF(backingSize); 3325 Matrix uvMatrix(aRect.width, 0.0f, 0.0f, aRect.height, aRect.x, aRect.y); 3326 uvMatrix *= invMatrix; 3327 uvMatrix *= Matrix(1.0f / backingSizeF.width, 0.0f, 0.0f, 3328 1.0f / backingSizeF.height, 3329 float(bounds.x - offset.x) / backingSizeF.width, 3330 float(bounds.y - offset.y) / backingSizeF.height); 3331 Array<float, 6> uvData = {uvMatrix._11, uvMatrix._12, uvMatrix._21, 3332 uvMatrix._22, uvMatrix._31, uvMatrix._32}; 3333 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mImageProgramTexMatrix, uvData, 3334 mImageProgramUniformState.mTexMatrix); 3335 3336 // Clamp sampling to within the bounds of the backing texture subrect. 3337 Array<float, 4> texBounds = { 3338 (bounds.x + 0.5f) / backingSizeF.width, 3339 (bounds.y + 0.5f) / backingSizeF.height, 3340 (bounds.XMost() - 0.5f) / backingSizeF.width, 3341 (bounds.YMost() - 0.5f) / backingSizeF.height, 3342 }; 3343 switch (surfacePattern.mExtendMode) { 3344 case ExtendMode::REPEAT: 3345 texBounds[0] = -1e16f; 3346 texBounds[1] = -1e16f; 3347 texBounds[2] = 1e16f; 3348 texBounds[3] = 1e16f; 3349 break; 3350 case ExtendMode::REPEAT_X: 3351 texBounds[0] = -1e16f; 3352 texBounds[2] = 1e16f; 3353 break; 3354 case ExtendMode::REPEAT_Y: 3355 texBounds[1] = -1e16f; 3356 texBounds[3] = 1e16f; 3357 break; 3358 default: 3359 break; 3360 } 3361 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mImageProgramTexBounds, texBounds, 3362 mImageProgramUniformState.mTexBounds); 3363 3364 // Ensure we use nearest filtering when no antialiasing is requested. 3365 if (UseNearestFilter(surfacePattern)) { 3366 SetTexFilter(tex, false); 3367 } 3368 3369 // Finally draw the image rectangle. 3370 if (aVertexRange) { 3371 // If there's a vertex range, then we need to draw triangles within from 3372 // generated from a path stored in the path vertex buffer. 3373 DrawTriangles(*aVertexRange); 3374 } else { 3375 // Otherwise we're drawing a simple filled rectangle. 3376 DrawQuad(); 3377 } 3378 3379 // Restore the default linear filter if overridden. 3380 if (UseNearestFilter(surfacePattern)) { 3381 SetTexFilter(tex, true); 3382 } 3383 3384 success = true; 3385 break; 3386 } 3387 default: 3388 gfxWarning() << "Unknown DrawTargetWebgl::DrawRect pattern type: " 3389 << (int)aPattern.GetType(); 3390 break; 3391 } 3392 3393 return success; 3394 } 3395 3396 // Get an appropriate input texture for a given surface within the source rect. 3397 already_AddRefed<WebGLTexture> SharedContextWebgl::GetFilterInputTexture( 3398 const RefPtr<SourceSurface>& aSurface, const IntRect& aSourceRect, 3399 RefPtr<TextureHandle>* aHandle, IntPoint& aOffset, SurfaceFormat& aFormat, 3400 IntRect& aBounds, IntSize& aBackingSize) { 3401 // If a texture handle was supplied, or if the surface already has an 3402 // assigned texture handle stashed in its used data, try to use it. 3403 RefPtr<SourceSurface> underlyingSurface = 3404 aSurface ? aSurface->GetUnderlyingSurface() : nullptr; 3405 RefPtr<TextureHandle> handle = 3406 aHandle ? aHandle->get() 3407 : (underlyingSurface 3408 ? static_cast<TextureHandle*>( 3409 underlyingSurface->GetUserData(&mTextureHandleKey)) 3410 : nullptr); 3411 IntSize texSize; 3412 IntPoint offset; 3413 SurfaceFormat format; 3414 // Check if the found handle is still valid. 3415 if (handle && handle->IsValid() && 3416 (aSourceRect.IsEmpty() || 3417 handle->GetSamplingRect().IsEqualEdges(aSourceRect))) { 3418 texSize = handle->GetSize(); 3419 format = handle->GetFormat(); 3420 offset = handle->GetSamplingOffset(); 3421 } else { 3422 // Otherwise, there is no handle that can be used yet, so extract 3423 // information from the surface pattern. 3424 handle = nullptr; 3425 if (!underlyingSurface) { 3426 // If there was no actual surface supplied, then we tried to draw 3427 // using a texture handle, but the texture handle wasn't valid. 3428 return nullptr; 3429 } 3430 texSize = underlyingSurface->GetSize(); 3431 format = underlyingSurface->GetFormat(); 3432 if (!aSourceRect.IsEmpty()) { 3433 texSize = aSourceRect.Size(); 3434 offset = aSourceRect.TopLeft(); 3435 } 3436 } 3437 3438 RefPtr<WebGLTexture> tex; 3439 IntRect bounds; 3440 IntSize backingSize; 3441 if (handle) { 3442 // If using an existing handle, move it to the front of the MRU list. 3443 handle->remove(); 3444 mTextureHandles.insertFront(handle); 3445 // Count reusing a snapshot texture (no readback) as a cache hit. 3446 mCurrentTarget->mProfile.OnCacheHit(); 3447 } else if ((tex = GetCompatibleSnapshot(underlyingSurface, &handle))) { 3448 backingSize = underlyingSurface->GetSize(); 3449 bounds = IntRect(offset, texSize); 3450 // Count reusing a snapshot texture (no readback) as a cache hit. 3451 mCurrentTarget->mProfile.OnCacheHit(); 3452 } else { 3453 // If we get here, we need a data surface for a texture upload. 3454 RefPtr<DataSourceSurface> data = underlyingSurface->GetDataSurface(); 3455 if (!data) { 3456 return nullptr; 3457 } 3458 // There is no existing handle. Try to allocate a new one. If the 3459 // surface size may change via a forced update, then don't allocate 3460 // from a shared texture page. 3461 handle = AllocateTextureHandle(format, texSize); 3462 if (!handle) { 3463 MOZ_ASSERT(false); 3464 return nullptr; 3465 } 3466 UploadSurfaceToHandle(data, offset, handle); 3467 // Link the handle to the surface's user data. 3468 handle->SetSamplingOffset(aSourceRect.TopLeft()); 3469 if (aHandle) { 3470 *aHandle = handle; 3471 } else { 3472 handle->SetSurface(underlyingSurface); 3473 underlyingSurface->AddUserData(&mTextureHandleKey, handle.get(), nullptr); 3474 } 3475 } 3476 3477 if (handle) { 3478 BackingTexture* backing = handle->GetBackingTexture(); 3479 if (!tex) { 3480 tex = backing->GetWebGLTexture(); 3481 } 3482 bounds = bounds.IsEmpty() ? handle->GetBounds() 3483 : handle->GetBounds().SafeIntersect( 3484 bounds + handle->GetBounds().TopLeft()); 3485 backingSize = backing->GetSize(); 3486 } 3487 3488 aOffset = offset; 3489 aFormat = format; 3490 aBounds = bounds; 3491 aBackingSize = backingSize; 3492 return tex.forget(); 3493 } 3494 3495 // Implements any filter that can be computed with a 5x4 color matrix. 3496 bool SharedContextWebgl::FilterRect(const Rect& aDestRect, 3497 const Matrix5x4& aColorMatrix, 3498 const RefPtr<SourceSurface>& aSurface, 3499 const IntRect& aSourceRect, 3500 const DrawOptions& aOptions, 3501 RefPtr<TextureHandle>* aHandle, 3502 RefPtr<TextureHandle>* aTargetHandle) { 3503 if (!aTargetHandle && !mCurrentTarget->MarkChanged()) { 3504 return false; 3505 } 3506 3507 IntPoint offset; 3508 SurfaceFormat format; 3509 IntRect bounds; 3510 IntSize backingSize; 3511 RefPtr<WebGLTexture> tex = GetFilterInputTexture( 3512 aSurface, aSourceRect, aHandle, offset, format, bounds, backingSize); 3513 if (!tex) { 3514 return false; 3515 } 3516 3517 IntSize viewportSize = mViewportSize; 3518 bool needTarget = !!aTargetHandle; 3519 if (needTarget) { 3520 IntSize targetSize = IntSize::Ceil(aDestRect.Size()); 3521 viewportSize = targetSize; 3522 // Blur filters need to render to a color target, whereas shadows will only 3523 // sample alpha. 3524 // If sourcing from a texture handle as input, be careful not to render to 3525 // a handle with the same exact backing texture, which is not allowed in 3526 // WebGL. 3527 RefPtr<TextureHandle> targetHandle = 3528 AllocateTextureHandle(format, targetSize, true, true, tex); 3529 if (!targetHandle) { 3530 MOZ_ASSERT(false); 3531 return false; 3532 } 3533 3534 *aTargetHandle = targetHandle; 3535 3536 BindScratchFramebuffer(targetHandle, true, targetSize); 3537 3538 SetBlendState(CompositionOp::OP_OVER); 3539 } else { 3540 // Set up the scissor test to reflect the clipping rectangle, if supplied. 3541 if (!mClipRect.Contains(IntRect(IntPoint(), mViewportSize))) { 3542 EnableScissor(mClipRect); 3543 } else { 3544 DisableScissor(); 3545 } 3546 3547 // Map the composition op to a WebGL blend mode, if possible. 3548 SetBlendState(aOptions.mCompositionOp); 3549 } 3550 3551 // Switch to the filter shader and set up relevant transforms. 3552 if (mLastProgram != mFilterProgram) { 3553 mWebgl->UseProgram(mFilterProgram); 3554 mLastProgram = mFilterProgram; 3555 } 3556 3557 Array<float, 2> viewportData = {float(viewportSize.width), 3558 float(viewportSize.height)}; 3559 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mFilterProgramViewport, viewportData, 3560 mFilterProgramUniformState.mViewport); 3561 3562 Rect xformRect; 3563 if (needTarget) { 3564 // If rendering to a temporary target for an intermediate pass, then fill 3565 // the entire framebuffer. 3566 xformRect = Rect(IntRect(IntPoint(), viewportSize)); 3567 } else { 3568 // If doing a final composite, then render to the requested rectangle. 3569 xformRect = aDestRect; 3570 } 3571 Array<float, 4> xformData = {xformRect.width, xformRect.height, xformRect.x, 3572 xformRect.y}; 3573 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mFilterProgramTransform, xformData, 3574 mFilterProgramUniformState.mTransform); 3575 3576 Rect clipRect; 3577 if (needTarget) { 3578 // Disable any AA clipping. 3579 clipRect = xformRect; 3580 } else { 3581 clipRect = mClipAARect; 3582 } 3583 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel 3584 // boundary. 3585 clipRect.Inflate(0.5f); 3586 Array<float, 4> clipData = {clipRect.x, clipRect.y, clipRect.XMost(), 3587 clipRect.YMost()}; 3588 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mFilterProgramClipBounds, clipData, 3589 mFilterProgramUniformState.mClipBounds); 3590 3591 Array<float, 16> colorMatData = { 3592 aColorMatrix._11, aColorMatrix._12, aColorMatrix._13, aColorMatrix._14, 3593 aColorMatrix._21, aColorMatrix._22, aColorMatrix._23, aColorMatrix._24, 3594 aColorMatrix._31, aColorMatrix._32, aColorMatrix._33, aColorMatrix._34, 3595 aColorMatrix._41, aColorMatrix._42, aColorMatrix._43, aColorMatrix._44}; 3596 MaybeUniformData(LOCAL_GL_FLOAT_MAT4, mFilterProgramColorMatrix, colorMatData, 3597 mFilterProgramUniformState.mColorMatrix); 3598 Array<float, 4> colorOffData = {aColorMatrix._51, aColorMatrix._52, 3599 aColorMatrix._53, aColorMatrix._54}; 3600 MaybeUniformData(LOCAL_GL_FLOAT_MAT4, mFilterProgramColorOffset, colorOffData, 3601 mFilterProgramUniformState.mColorOffset); 3602 3603 // Start binding the WebGL state for the texture. 3604 if (mLastTexture != tex) { 3605 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex); 3606 mLastTexture = tex; 3607 } 3608 3609 // Set up the texture coordinate matrix to map from the input rectangle to 3610 // the backing texture subrect. 3611 Size backingSizeF(backingSize); 3612 Rect uvXform((bounds.x - offset.x) / backingSizeF.width, 3613 (bounds.y - offset.y) / backingSizeF.height, 3614 xformRect.width / backingSizeF.width, 3615 xformRect.height / backingSizeF.height); 3616 Array<float, 4> uvData = {uvXform.width, uvXform.height, uvXform.x, 3617 uvXform.y}; 3618 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mFilterProgramTexMatrix, uvData, 3619 mFilterProgramUniformState.mTexMatrix); 3620 3621 // Bounds for inclusion testing. These are not offset by half a pixel because 3622 // they are not used for clamping, but rather denote pixel thresholds. 3623 Array<float, 4> texBounds = { 3624 bounds.x / backingSizeF.width, 3625 bounds.y / backingSizeF.height, 3626 bounds.XMost() / backingSizeF.width, 3627 bounds.YMost() / backingSizeF.height, 3628 }; 3629 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mFilterProgramTexBounds, texBounds, 3630 mFilterProgramUniformState.mTexBounds); 3631 3632 RefPtr<WebGLTexture> prevClipMask; 3633 if (needTarget) { 3634 // Ensure the current clip mask is ignored. 3635 prevClipMask = mLastClipMask; 3636 SetNoClipMask(); 3637 } 3638 3639 DrawQuad(); 3640 3641 if (needTarget) { 3642 // Restore the previous framebuffer state. 3643 RestoreCurrentTarget(prevClipMask); 3644 } 3645 3646 return true; 3647 } 3648 3649 // Filters a surface and draws the result at the specified offset. 3650 bool DrawTargetWebgl::FilterSurface(const Matrix5x4& aColorMatrix, 3651 SourceSurface* aSurface, 3652 const IntRect& aSourceRect, 3653 const Point& aDest, 3654 const DrawOptions& aOptions) { 3655 IntRect sourceRect = 3656 aSourceRect.IsEmpty() ? aSurface->GetRect() : aSourceRect; 3657 if (ShouldAccelPath(aOptions, nullptr, 3658 Rect(aDest, Size(sourceRect.Size())))) { 3659 if (mTransform.IsTranslation() && 3660 !mSharedContext->RequiresMultiStageBlend(aOptions, this)) { 3661 // If the transform doesn't require resampling and the blend op is simple, 3662 // then draw the filter directly to the canvas. 3663 return mSharedContext->FilterRect( 3664 Rect(aDest + mTransform.GetTranslation(), Size(sourceRect.Size())), 3665 aColorMatrix, aSurface, sourceRect, aOptions, nullptr, nullptr); 3666 } 3667 // There is a complex transform or blend op, so the filter must be drawn to 3668 // an intermediate texture first before resampling. 3669 RefPtr<TextureHandle> resultHandle; 3670 if (mSharedContext->FilterRect(Rect(Point(0, 0), Size(sourceRect.Size())), 3671 aColorMatrix, aSurface, sourceRect, 3672 DrawOptions(), nullptr, &resultHandle) && 3673 resultHandle) { 3674 SurfacePattern filterPattern(nullptr, ExtendMode::CLAMP, 3675 Matrix::Translation(aDest)); 3676 return mSharedContext->DrawRectAccel( 3677 Rect(aDest, Size(resultHandle->GetSize())), filterPattern, aOptions, 3678 Nothing(), &resultHandle, true, true, true); 3679 } 3680 } 3681 return false; 3682 } 3683 3684 // Provides a single pass of a separable blur. 3685 bool SharedContextWebgl::BlurRectPass( 3686 const Rect& aDestRect, const Point& aSigma, bool aHorizontal, 3687 const RefPtr<SourceSurface>& aSurface, const IntRect& aSourceRect, 3688 const DrawOptions& aOptions, Maybe<DeviceColor> aMaskColor, 3689 RefPtr<TextureHandle>* aHandle, RefPtr<TextureHandle>* aTargetHandle, 3690 bool aFilter) { 3691 IntPoint offset; 3692 SurfaceFormat format; 3693 IntRect bounds; 3694 IntSize backingSize; 3695 RefPtr<WebGLTexture> tex = GetFilterInputTexture( 3696 aSurface, aSourceRect, aHandle, offset, format, bounds, backingSize); 3697 if (!tex) { 3698 return false; 3699 } 3700 3701 IntSize viewportSize = mViewportSize; 3702 IntSize blurRadius(BLUR_ACCEL_RADIUS(aSigma.x), BLUR_ACCEL_RADIUS(aSigma.y)); 3703 bool needTarget = !!aTargetHandle; 3704 if (needTarget) { 3705 // For the initial horizontal pass, and also for the second pass of filters, 3706 // we need to render to a temporary framebuffer that has been inflated to 3707 // accommodate blurred pixels in the margins. 3708 IntSize targetSize( 3709 int(ceil(aDestRect.width)) + blurRadius.width * 2, 3710 aHorizontal ? bounds.height 3711 : int(ceil(aDestRect.height)) + blurRadius.height * 2); 3712 viewportSize = targetSize; 3713 // Blur filters need to render to a color target, whereas shadows will only 3714 // sample alpha. 3715 // If sourcing from a texture handle as input, be careful not to render to 3716 // a handle with the same exact backing texture, which is not allowed in 3717 // WebGL. 3718 RefPtr<TextureHandle> targetHandle = AllocateTextureHandle( 3719 aFilter ? format : SurfaceFormat::A8, targetSize, true, true, tex); 3720 if (!targetHandle) { 3721 MOZ_ASSERT(false); 3722 return false; 3723 } 3724 3725 *aTargetHandle = targetHandle; 3726 3727 BindScratchFramebuffer(targetHandle, true, targetSize); 3728 3729 SetBlendState(CompositionOp::OP_OVER); 3730 } else { 3731 // Set up the scissor test to reflect the clipping rectangle, if supplied. 3732 if (!mClipRect.Contains(IntRect(IntPoint(), mViewportSize))) { 3733 EnableScissor(mClipRect); 3734 } else { 3735 DisableScissor(); 3736 } 3737 3738 // Map the composition op to a WebGL blend mode, if possible. 3739 SetBlendState(aOptions.mCompositionOp); 3740 } 3741 3742 // Switch to the blur shader and set up relevant transforms. 3743 if (mLastProgram != mBlurProgram) { 3744 mWebgl->UseProgram(mBlurProgram); 3745 mLastProgram = mBlurProgram; 3746 } 3747 3748 Array<float, 2> viewportData = {float(viewportSize.width), 3749 float(viewportSize.height)}; 3750 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mBlurProgramViewport, viewportData, 3751 mBlurProgramUniformState.mViewport); 3752 3753 Rect xformRect; 3754 if (needTarget) { 3755 // If rendering to a temporary target for an intermediate pass, then fill 3756 // the entire framebuffer. 3757 xformRect = Rect(IntRect(IntPoint(), viewportSize)); 3758 } else { 3759 // If doing a final composite, then render to the requested rectangle, 3760 // inflated for the blurred margin pixels. 3761 xformRect = aDestRect; 3762 xformRect.Inflate(Size(blurRadius)); 3763 } 3764 Array<float, 4> xformData = {xformRect.width, xformRect.height, xformRect.x, 3765 xformRect.y}; 3766 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mBlurProgramTransform, xformData, 3767 mBlurProgramUniformState.mTransform); 3768 3769 Rect clipRect; 3770 if (needTarget) { 3771 // Disable any AA clipping. 3772 clipRect = xformRect; 3773 } else { 3774 clipRect = mClipAARect; 3775 } 3776 // Offset the clip AA bounds by 0.5 to ensure AA falls to 0 at pixel 3777 // boundary. 3778 clipRect.Inflate(0.5f); 3779 Array<float, 4> clipData = {clipRect.x, clipRect.y, clipRect.XMost(), 3780 clipRect.YMost()}; 3781 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mBlurProgramClipBounds, clipData, 3782 mBlurProgramUniformState.mClipBounds); 3783 3784 DeviceColor color = 3785 needTarget ? DeviceColor(1, 1, 1, 1) 3786 : PremultiplyColor(aMaskColor.valueOr(DeviceColor(1, 1, 1, 1)), 3787 aOptions.mAlpha); 3788 Array<float, 4> colorData = {color.b, color.g, color.r, color.a}; 3789 Array<float, 1> swizzleData = {format == SurfaceFormat::A8 ? 1.0f : 0.0f}; 3790 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mBlurProgramColor, colorData, 3791 mBlurProgramUniformState.mColor); 3792 MaybeUniformData(LOCAL_GL_FLOAT, mBlurProgramSwizzle, swizzleData, 3793 mBlurProgramUniformState.mSwizzle); 3794 3795 // Start binding the WebGL state for the texture. 3796 if (mLastTexture != tex) { 3797 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex); 3798 mLastTexture = tex; 3799 } 3800 3801 // Set up the texture coordinate matrix to map from the input rectangle to 3802 // the backing texture subrect. 3803 Size backingSizeF(backingSize); 3804 Rect uvXform((bounds.x - offset.x - (xformRect.width - bounds.width) / 2) / 3805 backingSizeF.width, 3806 (bounds.y - offset.y - (xformRect.height - bounds.height) / 2) / 3807 backingSizeF.height, 3808 xformRect.width / backingSizeF.width, 3809 xformRect.height / backingSizeF.height); 3810 Array<float, 4> uvData = {uvXform.width, uvXform.height, uvXform.x, 3811 uvXform.y}; 3812 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mBlurProgramTexMatrix, uvData, 3813 mBlurProgramUniformState.mTexMatrix); 3814 3815 // Bounds for inclusion testing. These are not offset by half a pixel because 3816 // they are not used for clamping, but rather denote pixel thresholds. 3817 Array<float, 4> texBounds = { 3818 bounds.x / backingSizeF.width, 3819 bounds.y / backingSizeF.height, 3820 bounds.XMost() / backingSizeF.width, 3821 bounds.YMost() / backingSizeF.height, 3822 }; 3823 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mBlurProgramTexBounds, texBounds, 3824 mBlurProgramUniformState.mTexBounds); 3825 3826 Array<float, 1> sigmaData = {aHorizontal ? aSigma.x : aSigma.y}; 3827 MaybeUniformData(LOCAL_GL_FLOAT, mBlurProgramSigma, sigmaData, 3828 mBlurProgramUniformState.mSigma); 3829 3830 Array<float, 2> offsetScale = 3831 aHorizontal ? Array<float, 2>{1.0f / backingSizeF.width, 0.0f} 3832 : Array<float, 2>{0.0f, 1.0f / backingSizeF.height}; 3833 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mBlurProgramOffsetScale, offsetScale, 3834 mBlurProgramUniformState.mOffsetScale); 3835 3836 RefPtr<WebGLTexture> prevClipMask; 3837 if (needTarget) { 3838 // Ensure the current clip mask is ignored. 3839 prevClipMask = mLastClipMask; 3840 SetNoClipMask(); 3841 } 3842 3843 DrawQuad(); 3844 3845 if (needTarget) { 3846 // Restore the previous framebuffer state. 3847 RestoreCurrentTarget(prevClipMask); 3848 } 3849 3850 return true; 3851 } 3852 3853 // Utility function to schedule multiple blur passes of a separable blur. 3854 bool SharedContextWebgl::BlurRectAccel( 3855 const Rect& aDestRect, const Point& aSigma, 3856 const RefPtr<SourceSurface>& aSurface, const IntRect& aSourceRect, 3857 const DrawOptions& aOptions, Maybe<DeviceColor> aMaskColor, 3858 RefPtr<TextureHandle>* aHandle, RefPtr<TextureHandle>* aTargetHandle, 3859 RefPtr<TextureHandle>* aResultHandle, bool aFilter) { 3860 if (!aResultHandle && !mCurrentTarget->MarkChanged()) { 3861 return false; 3862 } 3863 RefPtr<TextureHandle> targetHandle = 3864 aTargetHandle ? aTargetHandle->get() : nullptr; 3865 if (targetHandle && targetHandle->IsValid() && 3866 BlurRectPass(aDestRect, aSigma, false, nullptr, IntRect(), aOptions, 3867 aMaskColor, &targetHandle, aResultHandle, aFilter)) { 3868 return true; 3869 } 3870 3871 RefPtr<TextureHandle> handle = aHandle ? aHandle->get() : nullptr; 3872 if (BlurRectPass(aDestRect, aSigma, true, aSurface, aSourceRect, 3873 DrawOptions(), Nothing(), &handle, &targetHandle, aFilter) && 3874 targetHandle && 3875 BlurRectPass(aDestRect, aSigma, false, nullptr, IntRect(), aOptions, 3876 aMaskColor, &targetHandle, aResultHandle, aFilter)) { 3877 if (aHandle) { 3878 *aHandle = handle.forget(); 3879 } 3880 if (aTargetHandle) { 3881 *aTargetHandle = targetHandle.forget(); 3882 } 3883 return true; 3884 } 3885 return false; 3886 } 3887 3888 // Halves the dimensions of a blur input texture for sufficiently large blurs 3889 // where scaling won't significantly impact resultant blur quality. 3890 already_AddRefed<SourceSurface> SharedContextWebgl::DownscaleBlurInput( 3891 SourceSurface* aSurface, const IntRect& aSourceRect, int aIters) { 3892 if (std::max(aSourceRect.width, aSourceRect.height) <= 1) { 3893 return nullptr; 3894 } 3895 RefPtr<TextureHandle> fullHandle; 3896 // First check if the source surface is actually a texture. 3897 if (RefPtr<WebGLTexture> fullTex = 3898 GetCompatibleSnapshot(aSurface, &fullHandle)) { 3899 IntRect sourceRect = aSourceRect; 3900 for (int i = 0; i < aIters; ++i) { 3901 IntSize halfSize = (sourceRect.Size() + IntSize(1, 1)) / 2; 3902 // Allocate a half-size texture for the downscale target. 3903 RefPtr<TextureHandle> halfHandle = AllocateTextureHandle( 3904 aSurface->GetFormat(), halfSize, true, true, fullTex); 3905 if (!halfHandle) { 3906 break; 3907 } 3908 3909 // Set up the read framebuffer for the full-size texture. 3910 if (!mScratchFramebuffer) { 3911 mScratchFramebuffer = mWebgl->CreateFramebuffer(); 3912 } 3913 mWebgl->BindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mScratchFramebuffer); 3914 webgl::FbAttachInfo readInfo; 3915 readInfo.tex = fullTex; 3916 mWebgl->FramebufferAttach(LOCAL_GL_READ_FRAMEBUFFER, 3917 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D, 3918 readInfo); 3919 3920 // Set up the draw framebuffer for the half-size texture. 3921 if (!mTargetFramebuffer) { 3922 mTargetFramebuffer = mWebgl->CreateFramebuffer(); 3923 } 3924 BackingTexture* halfBacking = halfHandle->GetBackingTexture(); 3925 InitRenderTex(halfBacking); 3926 3927 mWebgl->BindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, mTargetFramebuffer); 3928 webgl::FbAttachInfo drawInfo; 3929 drawInfo.tex = halfBacking->GetWebGLTexture(); 3930 mWebgl->FramebufferAttach(LOCAL_GL_DRAW_FRAMEBUFFER, 3931 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D, 3932 drawInfo); 3933 3934 IntRect halfBounds = halfHandle->GetBounds(); 3935 EnableScissor(halfBounds, true); 3936 if (!halfBacking->IsInitialized()) { 3937 halfBacking->MarkInitialized(); 3938 // WebGL implicitly clears the backing texture the first time it is 3939 // used. 3940 } else if (i == 0 && !aSurface->GetRect().Contains(sourceRect)) { 3941 // Only clear if the blit does not completely fill the framebuffer. 3942 ClearRenderTex(halfBacking); 3943 } 3944 3945 // Do a linear-scaled blit from the full-size to the half-size texture. 3946 IntRect fullBounds = sourceRect; 3947 if (fullHandle) { 3948 fullBounds += fullHandle->GetBounds().TopLeft(); 3949 } 3950 mWebgl->AsWebGL2()->BlitFramebuffer( 3951 fullBounds.x, fullBounds.y, fullBounds.XMost(), fullBounds.YMost(), 3952 halfBounds.x, halfBounds.y, halfBounds.XMost(), halfBounds.YMost(), 3953 LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_LINEAR); 3954 3955 fullHandle = halfHandle; 3956 fullTex = halfBacking->GetWebGLTexture(); 3957 sourceRect = IntRect(IntPoint(), halfBounds.Size()); 3958 } 3959 RestoreCurrentTarget(); 3960 if (fullHandle) { 3961 if (sourceRect.IsEqualEdges(aSourceRect)) { 3962 return nullptr; 3963 } 3964 // Wrap the half-size texture with a surface. 3965 RefPtr<SourceSurfaceWebgl> surface = new SourceSurfaceWebgl(this); 3966 surface->SetHandle(fullHandle); 3967 return surface.forget(); 3968 } 3969 } 3970 3971 // If the full-size source surface is not actually a texture, downscale it 3972 // with a software filter as it will still improve upload performance while 3973 // reducing the final blur cost. 3974 IntRect sourceRect = aSourceRect; 3975 RefPtr<SourceSurface> fullSurface = aSurface; 3976 for (int i = 0; i < aIters; ++i) { 3977 IntSize halfSize = (sourceRect.Size() + IntSize(1, 1)) / 2; 3978 RefPtr<DrawTarget> halfDT = Factory::CreateDrawTarget( 3979 BackendType::SKIA, halfSize, aSurface->GetFormat()); 3980 if (!halfDT) { 3981 break; 3982 } 3983 halfDT->DrawSurface( 3984 fullSurface, Rect(halfDT->GetRect()), Rect(sourceRect), 3985 DrawSurfaceOptions(SamplingFilter::LINEAR), 3986 DrawOptions(1.0f, aSurface->GetFormat() == SurfaceFormat::A8 3987 ? CompositionOp::OP_OVER 3988 : CompositionOp::OP_SOURCE)); 3989 RefPtr<SourceSurface> halfSurface = halfDT->Snapshot(); 3990 if (!halfSurface) { 3991 break; 3992 } 3993 fullSurface = halfSurface; 3994 sourceRect = halfSurface->GetRect(); 3995 } 3996 if (sourceRect.IsEqualEdges(aSourceRect)) { 3997 return nullptr; 3998 } 3999 return fullSurface.forget(); 4000 } 4001 4002 // Blurs a surface and draws the result at the specified offset. 4003 bool DrawTargetWebgl::BlurSurface(float aSigma, SourceSurface* aSurface, 4004 const IntRect& aSourceRect, 4005 const Point& aDest, 4006 const DrawOptions& aOptions, 4007 const DeviceColor& aColor) { 4008 IntRect sourceRect = 4009 aSourceRect.IsEmpty() ? aSurface->GetRect() : aSourceRect; 4010 if (aSigma >= 0.0f && aSigma <= BLUR_ACCEL_SIGMA_MAX && 4011 ShouldAccelPath(aOptions, nullptr, 4012 Rect(aDest, Size(sourceRect.Size())))) { 4013 Maybe<DeviceColor> maskColor = 4014 aSurface->GetFormat() == SurfaceFormat::A8 ? Some(aColor) : Nothing(); 4015 if (aSigma < BLUR_ACCEL_SIGMA_MIN) { 4016 SurfacePattern maskPattern(aSurface, ExtendMode::CLAMP, 4017 Matrix::Translation(aDest)); 4018 if (!sourceRect.IsEqualEdges(aSurface->GetRect())) { 4019 maskPattern.mSamplingRect = sourceRect; 4020 } 4021 return DrawRect(Rect(aDest, Size(sourceRect.Size())), maskPattern, 4022 aOptions, maskColor); 4023 } 4024 // For large blurs, attempt to downscale the input texture so that 4025 // the blur radius can also be reduced to help performance. 4026 if (aSigma >= BLUR_ACCEL_DOWNSCALE_SIGMA && 4027 std::max(sourceRect.width, sourceRect.height) >= 4028 BLUR_ACCEL_DOWNSCALE_SIZE) { 4029 if (RefPtr<SourceSurface> scaleSurf = mSharedContext->DownscaleBlurInput( 4030 aSurface, sourceRect, BLUR_ACCEL_DOWNSCALE_ITERS)) { 4031 // Approximate the large blur with a smaller blur that scales the sigma 4032 // proportionally to the surface size. 4033 IntSize scaleSize = scaleSurf->GetSize(); 4034 Point scale(float(sourceRect.width) / float(scaleSize.width), 4035 float(sourceRect.height) / float(scaleSize.height)); 4036 RefPtr<TextureHandle> resultHandle; 4037 if (mSharedContext->BlurRectAccel( 4038 Rect(scaleSurf->GetRect()), 4039 Point(aSigma / scale.x, aSigma / scale.y), scaleSurf, 4040 scaleSurf->GetRect(), DrawOptions(), Nothing(), nullptr, 4041 nullptr, &resultHandle, true) && 4042 resultHandle) { 4043 IntSize blurMargin = (resultHandle->GetSize() - scaleSize) / 2; 4044 Point blurOrigin = aDest - Point(blurMargin.width * scale.x, 4045 blurMargin.height * scale.y); 4046 SurfacePattern blurPattern( 4047 nullptr, ExtendMode::CLAMP, 4048 Matrix::Scaling(scale.x, scale.y).PostTranslate(blurOrigin)); 4049 return mSharedContext->DrawRectAccel( 4050 Rect(blurOrigin, 4051 Size(resultHandle->GetSize()) * Size(scale.x, scale.y)), 4052 blurPattern, 4053 DrawOptions(aOptions.mAlpha, aOptions.mCompositionOp, 4054 AntialiasMode::DEFAULT), 4055 maskColor, &resultHandle, true, true, true); 4056 } 4057 } 4058 } 4059 if (mTransform.IsTranslation() && 4060 !mSharedContext->RequiresMultiStageBlend(aOptions, this)) { 4061 return mSharedContext->BlurRectAccel( 4062 Rect(aDest + mTransform.GetTranslation(), Size(sourceRect.Size())), 4063 Point(aSigma, aSigma), aSurface, sourceRect, aOptions, maskColor, 4064 nullptr, nullptr, nullptr, true); 4065 } 4066 RefPtr<TextureHandle> resultHandle; 4067 if (mSharedContext->BlurRectAccel( 4068 Rect(Point(0, 0), Size(sourceRect.Size())), Point(aSigma, aSigma), 4069 aSurface, sourceRect, DrawOptions(), Nothing(), nullptr, nullptr, 4070 &resultHandle, true) && 4071 resultHandle) { 4072 IntSize blurMargin = (resultHandle->GetSize() - sourceRect.Size()) / 2; 4073 Point blurOrigin = aDest - Point(blurMargin.width, blurMargin.height); 4074 SurfacePattern blurPattern(nullptr, ExtendMode::CLAMP, 4075 Matrix::Translation(blurOrigin)); 4076 return mSharedContext->DrawRectAccel( 4077 Rect(blurOrigin, Size(resultHandle->GetSize())), blurPattern, 4078 aOptions, maskColor, &resultHandle, true, true, true); 4079 } 4080 } 4081 return false; 4082 } 4083 4084 static inline int RoundToFactor(int aDim, int aFactor) { 4085 // If the size is either greater than the factor or not power-of-two, round it 4086 // up to the round factor. 4087 int mask = aFactor - 1; 4088 return aDim > 1 && (aDim > mask || (aDim & (aDim - 1))) 4089 ? (aDim + mask) & ~mask 4090 : aDim; 4091 } 4092 4093 already_AddRefed<TextureHandle> SharedContextWebgl::ResolveFilterInputAccel( 4094 DrawTargetWebgl* aDT, const Path* aPath, const Pattern& aPattern, 4095 const IntRect& aSourceRect, const Matrix& aDestTransform, 4096 const DrawOptions& aOptions, const StrokeOptions* aStrokeOptions, 4097 SurfaceFormat aFormat) { 4098 if (SupportsDrawOptions(aOptions) != SupportsDrawOptionsStatus::Yes) { 4099 return nullptr; 4100 } 4101 if (IsContextLost()) { 4102 return nullptr; 4103 } 4104 // Round size to account for potential mipping from blur filters. 4105 int roundFactor = 2 << BLUR_ACCEL_DOWNSCALE_ITERS; 4106 IntSize roundSize = 4107 std::max(aSourceRect.width, aSourceRect.height) >= 4108 BLUR_ACCEL_DOWNSCALE_SIZE 4109 ? IntSize(RoundToFactor(aSourceRect.width, roundFactor), 4110 RoundToFactor(aSourceRect.height, roundFactor)) 4111 : aSourceRect.Size(); 4112 RefPtr<TextureHandle> handle = 4113 AllocateTextureHandle(aFormat, roundSize, true, true); 4114 if (!handle) { 4115 return nullptr; 4116 } 4117 4118 BackingTexture* targetBacking = handle->GetBackingTexture(); 4119 InitRenderTex(targetBacking); 4120 if (!aDT->PrepareContext(false, handle)) { 4121 return nullptr; 4122 } 4123 DisableScissor(); 4124 ClearRenderTex(targetBacking); 4125 4126 AutoRestoreTransform restore(aDT); 4127 aDT->SetTransform( 4128 Matrix(aDestTransform).PostTranslate(-aSourceRect.TopLeft())); 4129 4130 const SkPath& skiaPath = static_cast<const PathSkia*>(aPath)->GetPath(); 4131 SkRect skiaRect = SkRect::MakeEmpty(); 4132 // Draw the path as a simple rectangle with a supported pattern when 4133 // possible. 4134 if (!aStrokeOptions && skiaPath.isRect(&skiaRect)) { 4135 RectDouble rect = SkRectToRectDouble(skiaRect); 4136 RectDouble xformRect = aDT->TransformDouble(rect); 4137 if (aPattern.GetType() == PatternType::COLOR) { 4138 if (Maybe<Rect> clipped = aDT->RectClippedToViewport(xformRect)) { 4139 // If the pattern is transform-invariant and the rect clips to 4140 // the viewport, just clip drawing to the viewport to avoid 4141 // transform issues. 4142 if (DrawRectAccel(*clipped, aPattern, aOptions, Nothing(), nullptr, 4143 false, false, true)) { 4144 return handle.forget(); 4145 } 4146 return nullptr; 4147 } 4148 } 4149 if (RectInsidePrecisionLimits(xformRect)) { 4150 if (SupportsPattern(aPattern)) { 4151 if (DrawRectAccel(NarrowToFloat(rect), aPattern, aOptions, Nothing(), 4152 nullptr, true, true, true)) { 4153 return handle.forget(); 4154 } 4155 return nullptr; 4156 } 4157 if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) { 4158 if (Maybe<SurfacePattern> surface = 4159 aDT->LinearGradientToSurface(xformRect, aPattern)) { 4160 if (DrawRectAccel(NarrowToFloat(rect), *surface, aOptions, Nothing(), 4161 nullptr, true, true, true)) { 4162 return handle.forget(); 4163 } 4164 return nullptr; 4165 } 4166 } 4167 } 4168 } 4169 if (DrawPathAccel(aPath, aPattern, aOptions, aStrokeOptions)) { 4170 return handle.forget(); 4171 } 4172 return nullptr; 4173 } 4174 4175 already_AddRefed<SourceSurfaceWebgl> DrawTargetWebgl::ResolveFilterInputAccel( 4176 const Path* aPath, const Pattern& aPattern, const IntRect& aSourceRect, 4177 const Matrix& aDestTransform, const DrawOptions& aOptions, 4178 const StrokeOptions* aStrokeOptions, SurfaceFormat aFormat) { 4179 if (RefPtr<TextureHandle> handle = mSharedContext->ResolveFilterInputAccel( 4180 this, aPath, aPattern, aSourceRect, aDestTransform, aOptions, 4181 aStrokeOptions, aFormat)) { 4182 RefPtr<SourceSurfaceWebgl> surface = new SourceSurfaceWebgl(mSharedContext); 4183 surface->SetHandle(handle); 4184 return surface.forget(); 4185 } 4186 return nullptr; 4187 } 4188 4189 bool SharedContextWebgl::RemoveSharedTexture( 4190 const RefPtr<SharedTexture>& aTexture) { 4191 auto pos = 4192 std::find(mSharedTextures.begin(), mSharedTextures.end(), aTexture); 4193 if (pos == mSharedTextures.end()) { 4194 return false; 4195 } 4196 // Keep around a reserve of empty pages to avoid initialization costs from 4197 // allocating shared pages. If still below the limit of reserved pages, then 4198 // just add it to the reserve. Otherwise, erase the empty texture page. 4199 size_t maxBytes = StaticPrefs::gfx_canvas_accelerated_reserve_empty_cache() 4200 << 20; 4201 size_t totalEmpty = mEmptyTextureMemory + aTexture->UsedBytes(); 4202 if (totalEmpty <= maxBytes) { 4203 mEmptyTextureMemory = totalEmpty; 4204 } else { 4205 RemoveTextureMemory(aTexture); 4206 mSharedTextures.erase(pos); 4207 ClearLastTexture(); 4208 } 4209 return true; 4210 } 4211 4212 void SharedTextureHandle::Cleanup(SharedContextWebgl& aContext) { 4213 mTexture->Free(*this); 4214 4215 // Check if the shared handle's owning page has no more allocated handles 4216 // after we freed it. If so, remove the empty shared texture page also. 4217 if (!mTexture->HasAllocatedHandles()) { 4218 aContext.RemoveSharedTexture(mTexture); 4219 } 4220 } 4221 4222 bool SharedContextWebgl::RemoveStandaloneTexture( 4223 const RefPtr<StandaloneTexture>& aTexture) { 4224 auto pos = std::find(mStandaloneTextures.begin(), mStandaloneTextures.end(), 4225 aTexture); 4226 if (pos == mStandaloneTextures.end()) { 4227 return false; 4228 } 4229 RemoveTextureMemory(aTexture); 4230 mStandaloneTextures.erase(pos); 4231 ClearLastTexture(); 4232 return true; 4233 } 4234 4235 void StandaloneTexture::Cleanup(SharedContextWebgl& aContext) { 4236 aContext.RemoveStandaloneTexture(this); 4237 } 4238 4239 // Prune a given texture handle and release its associated resources. 4240 void SharedContextWebgl::PruneTextureHandle( 4241 const RefPtr<TextureHandle>& aHandle) { 4242 // Invalidate the handle so nothing will subsequently use its contents. 4243 aHandle->Invalidate(); 4244 // If the handle has an associated SourceSurface, unlink it. 4245 UnlinkSurfaceTexture(aHandle); 4246 // If the handle has an associated CacheEntry, unlink it. 4247 if (RefPtr<CacheEntry> entry = aHandle->GetCacheEntry()) { 4248 entry->Unlink(); 4249 } 4250 // Deduct the used space from the total. 4251 mUsedTextureMemory -= aHandle->UsedBytes(); 4252 // Ensure any allocated shared or standalone texture regions get freed. 4253 aHandle->Cleanup(*this); 4254 } 4255 4256 // Prune any texture memory above the limit (or margin below the limit) or any 4257 // least-recently-used handles that are no longer associated with any usable 4258 // surface. 4259 bool SharedContextWebgl::PruneTextureMemory(size_t aMargin, bool aPruneUnused) { 4260 // The maximum amount of texture memory that may be used by textures. 4261 size_t maxBytes = StaticPrefs::gfx_canvas_accelerated_cache_size() << 20; 4262 maxBytes -= std::min(maxBytes, aMargin); 4263 size_t maxItems = StaticPrefs::gfx_canvas_accelerated_cache_items(); 4264 size_t oldItems = mNumTextureHandles; 4265 while (!mTextureHandles.isEmpty() && 4266 (mUsedTextureMemory > maxBytes || mNumTextureHandles > maxItems || 4267 (aPruneUnused && !mTextureHandles.getLast()->IsUsed()))) { 4268 PruneTextureHandle(mTextureHandles.popLast()); 4269 --mNumTextureHandles; 4270 } 4271 return mNumTextureHandles < oldItems; 4272 } 4273 4274 // Attempt to convert a linear gradient to a 1D ramp texture. 4275 Maybe<SurfacePattern> DrawTargetWebgl::LinearGradientToSurface( 4276 const RectDouble& aBounds, const Pattern& aPattern) { 4277 MOZ_ASSERT(aPattern.GetType() == PatternType::LINEAR_GRADIENT); 4278 const auto& gradient = static_cast<const LinearGradientPattern&>(aPattern); 4279 // The gradient points must be transformed by the gradient's matrix. 4280 Point gradBegin = gradient.mMatrix.TransformPoint(gradient.mBegin); 4281 Point gradEnd = gradient.mMatrix.TransformPoint(gradient.mEnd); 4282 // Get the gradient points in user-space. 4283 Point begin = mTransform.TransformPoint(gradBegin); 4284 Point end = mTransform.TransformPoint(gradEnd); 4285 // Find the normalized direction of the gradient and its length. 4286 Point dir = end - begin; 4287 float len = dir.Length(); 4288 dir = dir / len; 4289 // Restrict the rendered bounds to fall within the canvas. 4290 Rect visBounds = NarrowToFloat(aBounds.SafeIntersect(RectDouble(GetRect()))); 4291 // Calculate the distances along the gradient direction of the bounds. 4292 float dist0 = (visBounds.TopLeft() - begin).DotProduct(dir); 4293 float distX = visBounds.width * dir.x; 4294 float distY = visBounds.height * dir.y; 4295 float minDist = floorf( 4296 std::max(dist0 + std::min(distX, 0.0f) + std::min(distY, 0.0f), 0.0f)); 4297 float maxDist = ceilf( 4298 std::min(dist0 + std::max(distX, 0.0f) + std::max(distY, 0.0f), len)); 4299 // Calculate the approximate size of the ramp texture, and see if it would be 4300 // sufficiently smaller than just rendering the primitive. 4301 float subLen = maxDist - minDist; 4302 if (subLen > 0 && subLen < 0.5f * visBounds.Area()) { 4303 // Create a 1D texture to contain the gradient ramp. Reserve two extra 4304 // texels at the beginning and end of the ramp to account for clamping. 4305 RefPtr<DrawTargetSkia> dt = new DrawTargetSkia; 4306 if (dt->Init(IntSize(int32_t(subLen + 2), 1), SurfaceFormat::B8G8R8A8)) { 4307 // Fill the section of the gradient ramp that is actually used. 4308 dt->FillRect(Rect(dt->GetRect()), 4309 LinearGradientPattern(Point(1 - minDist, 0.0f), 4310 Point(len + 1 - minDist, 0.0f), 4311 gradient.mStops)); 4312 if (RefPtr<SourceSurface> snapshot = dt->Snapshot()) { 4313 // Calculate a matrix that will map the gradient ramp texture onto the 4314 // actual direction of the gradient. 4315 Point gradDir = (gradEnd - gradBegin) / len; 4316 Point tangent = Point(-gradDir.y, gradDir.x) / gradDir.Length(); 4317 SurfacePattern surfacePattern( 4318 snapshot, ExtendMode::CLAMP, 4319 Matrix(gradDir.x, gradDir.y, tangent.x, tangent.y, gradBegin.x, 4320 gradBegin.y) 4321 .PreTranslate(minDist - 1, 0)); 4322 if (SupportsPattern(surfacePattern)) { 4323 return Some(surfacePattern); 4324 } 4325 } 4326 } 4327 } 4328 return Nothing(); 4329 } 4330 4331 void DrawTargetWebgl::FillRect(const Rect& aRect, const Pattern& aPattern, 4332 const DrawOptions& aOptions) { 4333 RectDouble xformRect = TransformDouble(aRect); 4334 if (aPattern.GetType() == PatternType::COLOR) { 4335 if (Maybe<Rect> clipped = RectClippedToViewport(xformRect)) { 4336 // If the pattern is transform-invariant and the rect clips to the 4337 // viewport, just clip drawing to the viewport to avoid transform 4338 // issues. 4339 DrawRect(*clipped, aPattern, aOptions, Nothing(), nullptr, false); 4340 return; 4341 } 4342 } 4343 if (RectInsidePrecisionLimits(xformRect)) { 4344 if (SupportsPattern(aPattern)) { 4345 DrawRect(aRect, aPattern, aOptions); 4346 return; 4347 } 4348 if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) { 4349 if (Maybe<SurfacePattern> surface = 4350 LinearGradientToSurface(xformRect, aPattern)) { 4351 if (DrawRect(aRect, *surface, aOptions, Nothing(), nullptr, true, true, 4352 true)) { 4353 return; 4354 } 4355 } 4356 } 4357 } 4358 4359 if (!mWebglValid) { 4360 MarkSkiaChanged(aOptions); 4361 mSkia->FillRect(aRect, aPattern, aOptions); 4362 } else { 4363 // If the pattern is unsupported, then transform the rect to a path so it 4364 // can be cached. 4365 SkPath skiaPath; 4366 skiaPath.addRect(RectToSkRect(aRect)); 4367 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING); 4368 DrawPath(path, aPattern, aOptions); 4369 } 4370 } 4371 4372 void CacheEntry::Link(const RefPtr<TextureHandle>& aHandle) { 4373 mHandle = aHandle; 4374 mHandle->SetCacheEntry(this); 4375 } 4376 4377 // When the CacheEntry becomes unused, it marks the corresponding 4378 // TextureHandle as unused and unlinks it from the CacheEntry. The 4379 // entry is removed from its containing Cache, if applicable. 4380 void CacheEntry::Unlink() { 4381 // The entry may not have a valid handle if rasterization failed. 4382 if (mHandle) { 4383 mHandle->SetCacheEntry(nullptr); 4384 mHandle = nullptr; 4385 } 4386 4387 RemoveFromList(); 4388 } 4389 4390 // Hashes a path and pattern to a single hash value that can be used for quick 4391 // comparisons. This currently avoids to expensive hashing of internal path 4392 // and pattern data for speed, relying instead on later exact comparisons for 4393 // disambiguation. 4394 HashNumber PathCacheEntry::HashPath(const QuantizedPath& aPath, 4395 const Pattern* aPattern, 4396 const Matrix& aTransform, 4397 const IntRect& aBounds, 4398 const Point& aOrigin) { 4399 HashNumber hash = 0; 4400 hash = AddToHash(hash, aPath.mPath.num_types); 4401 hash = AddToHash(hash, aPath.mPath.num_points); 4402 if (aPath.mPath.num_points > 0) { 4403 hash = AddToHash(hash, aPath.mPath.points[0].x); 4404 hash = AddToHash(hash, aPath.mPath.points[0].y); 4405 if (aPath.mPath.num_points > 1) { 4406 hash = AddToHash(hash, aPath.mPath.points[1].x); 4407 hash = AddToHash(hash, aPath.mPath.points[1].y); 4408 } 4409 } 4410 // Quantize the relative offset of the path to its bounds. 4411 IntPoint offset = RoundedToInt((aOrigin - Point(aBounds.TopLeft())) * 16.0f); 4412 hash = AddToHash(hash, offset.x); 4413 hash = AddToHash(hash, offset.y); 4414 hash = AddToHash(hash, aBounds.width); 4415 hash = AddToHash(hash, aBounds.height); 4416 if (aPattern) { 4417 hash = AddToHash(hash, (int)aPattern->GetType()); 4418 } 4419 return hash; 4420 } 4421 4422 // When caching rendered geometry, we need to ensure the scale and orientation 4423 // is approximately the same. The offset will be considered separately. 4424 static inline bool HasMatchingScale(const Matrix& aTransform1, 4425 const Matrix& aTransform2) { 4426 return FuzzyEqual(aTransform1._11, aTransform2._11) && 4427 FuzzyEqual(aTransform1._22, aTransform2._22) && 4428 FuzzyEqual(aTransform1._12, aTransform2._12) && 4429 FuzzyEqual(aTransform1._21, aTransform2._21); 4430 } 4431 4432 static const float kIgnoreSigma = 1e6f; 4433 4434 // Determines if an existing path cache entry matches an incoming path and 4435 // pattern. 4436 inline bool PathCacheEntry::MatchesPath( 4437 const QuantizedPath& aPath, const Pattern* aPattern, 4438 const StrokeOptions* aStrokeOptions, AAStrokeMode aStrokeMode, 4439 const Matrix& aTransform, const IntRect& aBounds, const Point& aOrigin, 4440 HashNumber aHash, float aSigma) { 4441 return aHash == mHash && HasMatchingScale(aTransform, mTransform) && 4442 // Ensure the clipped relative bounds fit inside those of the entry 4443 aBounds.x - aOrigin.x >= mBounds.x - mOrigin.x && 4444 (aBounds.x - aOrigin.x) + aBounds.width <= 4445 (mBounds.x - mOrigin.x) + mBounds.width && 4446 aBounds.y - aOrigin.y >= mBounds.y - mOrigin.y && 4447 (aBounds.y - aOrigin.y) + aBounds.height <= 4448 (mBounds.y - mOrigin.y) + mBounds.height && 4449 aPath == mPath && 4450 (!aPattern ? !mPattern : mPattern && *aPattern == *mPattern) && 4451 (!aStrokeOptions 4452 ? !mStrokeOptions 4453 : mStrokeOptions && *aStrokeOptions == *mStrokeOptions && 4454 mAAStrokeMode == aStrokeMode) && 4455 (aSigma == kIgnoreSigma || aSigma == mSigma); 4456 } 4457 4458 PathCacheEntry::PathCacheEntry(QuantizedPath&& aPath, Pattern* aPattern, 4459 StoredStrokeOptions* aStrokeOptions, 4460 AAStrokeMode aStrokeMode, 4461 const Matrix& aTransform, const IntRect& aBounds, 4462 const Point& aOrigin, HashNumber aHash, 4463 float aSigma) 4464 : CacheEntryImpl<PathCacheEntry>(aTransform, aBounds, aHash), 4465 mPath(std::move(aPath)), 4466 mOrigin(aOrigin), 4467 mPattern(aPattern), 4468 mStrokeOptions(aStrokeOptions), 4469 mAAStrokeMode(aStrokeMode), 4470 mSigma(aSigma) {} 4471 4472 // Attempt to find a matching entry in the path cache. If one isn't found, 4473 // a new entry will be created. The caller should check whether the contained 4474 // texture handle is valid to determine if it will need to render the text run 4475 // or just reuse the cached texture. 4476 already_AddRefed<PathCacheEntry> PathCache::FindOrInsertEntry( 4477 QuantizedPath aPath, const Pattern* aPattern, 4478 const StrokeOptions* aStrokeOptions, AAStrokeMode aStrokeMode, 4479 const Matrix& aTransform, const IntRect& aBounds, const Point& aOrigin, 4480 float aSigma) { 4481 HashNumber hash = 4482 PathCacheEntry::HashPath(aPath, aPattern, aTransform, aBounds, aOrigin); 4483 for (const RefPtr<PathCacheEntry>& entry : GetChain(hash)) { 4484 if (entry->MatchesPath(aPath, aPattern, aStrokeOptions, aStrokeMode, 4485 aTransform, aBounds, aOrigin, hash, aSigma)) { 4486 return do_AddRef(entry); 4487 } 4488 } 4489 Pattern* pattern = nullptr; 4490 if (aPattern) { 4491 pattern = aPattern->CloneWeak(); 4492 if (!pattern) { 4493 return nullptr; 4494 } 4495 } 4496 StoredStrokeOptions* strokeOptions = nullptr; 4497 if (aStrokeOptions) { 4498 strokeOptions = aStrokeOptions->Clone(); 4499 if (!strokeOptions) { 4500 return nullptr; 4501 } 4502 } 4503 RefPtr<PathCacheEntry> entry = 4504 new PathCacheEntry(std::move(aPath), pattern, strokeOptions, aStrokeMode, 4505 aTransform, aBounds, aOrigin, hash, aSigma); 4506 Insert(entry); 4507 return entry.forget(); 4508 } 4509 4510 // Attempt to find a matching entry in the path cache. If one isn't found, 4511 // just return failure and don't actually create an entry. 4512 already_AddRefed<PathCacheEntry> PathCache::FindEntry( 4513 const QuantizedPath& aPath, const Pattern* aPattern, 4514 const StrokeOptions* aStrokeOptions, AAStrokeMode aStrokeMode, 4515 const Matrix& aTransform, const IntRect& aBounds, const Point& aOrigin, 4516 float aSigma, bool aHasSecondaryHandle) { 4517 HashNumber hash = 4518 PathCacheEntry::HashPath(aPath, aPattern, aTransform, aBounds, aOrigin); 4519 for (const RefPtr<PathCacheEntry>& entry : GetChain(hash)) { 4520 if (entry->MatchesPath(aPath, aPattern, aStrokeOptions, aStrokeMode, 4521 aTransform, aBounds, aOrigin, hash, aSigma) && 4522 (!aHasSecondaryHandle || (entry->GetSecondaryHandle() && 4523 entry->GetSecondaryHandle()->IsValid()))) { 4524 return do_AddRef(entry); 4525 } 4526 } 4527 return nullptr; 4528 } 4529 4530 void DrawTargetWebgl::Fill(const Path* aPath, const Pattern& aPattern, 4531 const DrawOptions& aOptions) { 4532 if (!aPath || aPath->GetBackendType() != BackendType::SKIA) { 4533 return; 4534 } 4535 4536 const SkPath& skiaPath = static_cast<const PathSkia*>(aPath)->GetPath(); 4537 SkRect skiaRect = SkRect::MakeEmpty(); 4538 // Draw the path as a simple rectangle with a supported pattern when possible. 4539 if (skiaPath.isRect(&skiaRect)) { 4540 RectDouble rect = SkRectToRectDouble(skiaRect); 4541 RectDouble xformRect = TransformDouble(rect); 4542 if (aPattern.GetType() == PatternType::COLOR) { 4543 if (Maybe<Rect> clipped = RectClippedToViewport(xformRect)) { 4544 // If the pattern is transform-invariant and the rect clips to the 4545 // viewport, just clip drawing to the viewport to avoid transform 4546 // issues. 4547 DrawRect(*clipped, aPattern, aOptions, Nothing(), nullptr, false); 4548 return; 4549 } 4550 } 4551 4552 if (RectInsidePrecisionLimits(xformRect)) { 4553 if (SupportsPattern(aPattern)) { 4554 DrawRect(NarrowToFloat(rect), aPattern, aOptions); 4555 return; 4556 } 4557 if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) { 4558 if (Maybe<SurfacePattern> surface = 4559 LinearGradientToSurface(xformRect, aPattern)) { 4560 if (DrawRect(NarrowToFloat(rect), *surface, aOptions, Nothing(), 4561 nullptr, true, true, true)) { 4562 return; 4563 } 4564 } 4565 } 4566 } 4567 } 4568 4569 DrawPath(aPath, aPattern, aOptions); 4570 } 4571 4572 void DrawTargetWebgl::FillCircle(const Point& aOrigin, float aRadius, 4573 const Pattern& aPattern, 4574 const DrawOptions& aOptions) { 4575 DrawCircle(aOrigin, aRadius, aPattern, aOptions); 4576 } 4577 4578 QuantizedPath::QuantizedPath(const WGR::Path& aPath) : mPath(aPath) {} 4579 4580 QuantizedPath::QuantizedPath(QuantizedPath&& aPath) noexcept 4581 : mPath(aPath.mPath) { 4582 aPath.mPath.points = nullptr; 4583 aPath.mPath.num_points = 0; 4584 aPath.mPath.types = nullptr; 4585 aPath.mPath.num_types = 0; 4586 } 4587 4588 QuantizedPath::~QuantizedPath() { 4589 if (mPath.points || mPath.types) { 4590 WGR::wgr_path_release(mPath); 4591 } 4592 } 4593 4594 bool QuantizedPath::operator==(const QuantizedPath& aOther) const { 4595 return mPath.num_types == aOther.mPath.num_types && 4596 mPath.num_points == aOther.mPath.num_points && 4597 mPath.fill_mode == aOther.mPath.fill_mode && 4598 !memcmp(mPath.types, aOther.mPath.types, 4599 mPath.num_types * sizeof(uint8_t)) && 4600 !memcmp(mPath.points, aOther.mPath.points, 4601 mPath.num_points * sizeof(WGR::Point)); 4602 } 4603 4604 // Generate a quantized path from the Skia path using WGR. The supplied 4605 // transform will be applied to the path. The path is stored relative to its 4606 // bounds origin to support translation later. 4607 static Maybe<QuantizedPath> GenerateQuantizedPath( 4608 WGR::PathBuilder* aPathBuilder, const SkPath& aPath, const Rect& aBounds, 4609 const Matrix& aTransform) { 4610 if (!aPathBuilder) { 4611 return Nothing(); 4612 } 4613 4614 WGR::wgr_builder_reset(aPathBuilder); 4615 WGR::wgr_builder_set_fill_mode(aPathBuilder, 4616 aPath.getFillType() == SkPathFillType::kWinding 4617 ? WGR::FillMode::Winding 4618 : WGR::FillMode::EvenOdd); 4619 4620 SkPath::RawIter iter(aPath); 4621 SkPoint params[4]; 4622 SkPath::Verb currentVerb; 4623 4624 // printf_stderr("bounds: (%d, %d) %d x %d\n", aBounds.x, aBounds.y, 4625 // aBounds.width, aBounds.height); 4626 Matrix transform = aTransform; 4627 transform.PostTranslate(-aBounds.TopLeft()); 4628 while ((currentVerb = iter.next(params)) != SkPath::kDone_Verb) { 4629 switch (currentVerb) { 4630 case SkPath::kMove_Verb: { 4631 Point p0 = transform.TransformPoint(SkPointToPoint(params[0])); 4632 WGR::wgr_builder_move_to(aPathBuilder, p0.x, p0.y); 4633 break; 4634 } 4635 case SkPath::kLine_Verb: { 4636 Point p1 = transform.TransformPoint(SkPointToPoint(params[1])); 4637 WGR::wgr_builder_line_to(aPathBuilder, p1.x, p1.y); 4638 break; 4639 } 4640 case SkPath::kCubic_Verb: { 4641 Point p1 = transform.TransformPoint(SkPointToPoint(params[1])); 4642 Point p2 = transform.TransformPoint(SkPointToPoint(params[2])); 4643 Point p3 = transform.TransformPoint(SkPointToPoint(params[3])); 4644 // printf_stderr("cubic (%f, %f), (%f, %f), (%f, %f)\n", p1.x, p1.y, 4645 // p2.x, p2.y, p3.x, p3.y); 4646 WGR::wgr_builder_curve_to(aPathBuilder, p1.x, p1.y, p2.x, p2.y, p3.x, 4647 p3.y); 4648 break; 4649 } 4650 case SkPath::kQuad_Verb: { 4651 Point p1 = transform.TransformPoint(SkPointToPoint(params[1])); 4652 Point p2 = transform.TransformPoint(SkPointToPoint(params[2])); 4653 // printf_stderr("quad (%f, %f), (%f, %f)\n", p1.x, p1.y, p2.x, p2.y); 4654 WGR::wgr_builder_quad_to(aPathBuilder, p1.x, p1.y, p2.x, p2.y); 4655 break; 4656 } 4657 case SkPath::kConic_Verb: { 4658 Point p0 = transform.TransformPoint(SkPointToPoint(params[0])); 4659 Point p1 = transform.TransformPoint(SkPointToPoint(params[1])); 4660 Point p2 = transform.TransformPoint(SkPointToPoint(params[2])); 4661 float w = iter.conicWeight(); 4662 std::vector<Point> quads; 4663 int numQuads = ConvertConicToQuads(p0, p1, p2, w, quads); 4664 for (int i = 0; i < numQuads; i++) { 4665 Point q1 = quads[2 * i + 1]; 4666 Point q2 = quads[2 * i + 2]; 4667 // printf_stderr("conic quad (%f, %f), (%f, %f)\n", q1.x, q1.y, q2.x, 4668 // q2.y); 4669 WGR::wgr_builder_quad_to(aPathBuilder, q1.x, q1.y, q2.x, q2.y); 4670 } 4671 break; 4672 } 4673 case SkPath::kClose_Verb: 4674 // printf_stderr("close\n"); 4675 WGR::wgr_builder_close(aPathBuilder); 4676 break; 4677 default: 4678 MOZ_ASSERT(false); 4679 // Unexpected verb found in path! 4680 return Nothing(); 4681 } 4682 } 4683 4684 WGR::Path p = WGR::wgr_builder_get_path(aPathBuilder); 4685 if (!p.num_points || !p.num_types) { 4686 WGR::wgr_path_release(p); 4687 return Nothing(); 4688 } 4689 return Some(QuantizedPath(p)); 4690 } 4691 4692 // Get the output vertex buffer using WGR from an input quantized path. 4693 static Maybe<WGR::VertexBuffer> GeneratePathVertexBuffer( 4694 const QuantizedPath& aPath, const IntRect& aClipRect, 4695 bool aRasterizationTruncates, WGR::OutputVertex* aBuffer, 4696 size_t aBufferCapacity) { 4697 WGR::VertexBuffer vb = WGR::wgr_path_rasterize_to_tri_list( 4698 &aPath.mPath, aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height, 4699 true, false, aRasterizationTruncates, aBuffer, aBufferCapacity); 4700 if (!vb.len || (aBuffer && vb.len > aBufferCapacity)) { 4701 WGR::wgr_vertex_buffer_release(vb); 4702 return Nothing(); 4703 } 4704 return Some(vb); 4705 } 4706 4707 static inline AAStroke::LineJoin ToAAStrokeLineJoin(JoinStyle aJoin) { 4708 switch (aJoin) { 4709 case JoinStyle::BEVEL: 4710 return AAStroke::LineJoin::Bevel; 4711 case JoinStyle::ROUND: 4712 return AAStroke::LineJoin::Round; 4713 case JoinStyle::MITER: 4714 case JoinStyle::MITER_OR_BEVEL: 4715 return AAStroke::LineJoin::Miter; 4716 } 4717 return AAStroke::LineJoin::Miter; 4718 } 4719 4720 static inline AAStroke::LineCap ToAAStrokeLineCap(CapStyle aCap) { 4721 switch (aCap) { 4722 case CapStyle::BUTT: 4723 return AAStroke::LineCap::Butt; 4724 case CapStyle::ROUND: 4725 return AAStroke::LineCap::Round; 4726 case CapStyle::SQUARE: 4727 return AAStroke::LineCap::Square; 4728 } 4729 return AAStroke::LineCap::Butt; 4730 } 4731 4732 static inline Point WGRPointToPoint(const WGR::Point& aPoint) { 4733 // WGR points are 28.4 fixed-point where (0.0, 0.0) is assumed to be a pixel 4734 // center, as opposed to (0.5, 0.5) in canvas device space. WGR thus shifts 4735 // each point by (-0.5, -0.5). To undo this, transform from fixed-point back 4736 // to floating-point, and reverse the pixel shift by adding back (0.5, 0.5). 4737 return Point(IntPoint(aPoint.x, aPoint.y)) * (1.0f / 16.0f) + 4738 Point(0.5f, 0.5f); 4739 } 4740 4741 // Generates a vertex buffer for a stroked path using aa-stroke. 4742 static Maybe<AAStroke::VertexBuffer> GenerateStrokeVertexBuffer( 4743 const QuantizedPath& aPath, const StrokeOptions* aStrokeOptions, 4744 float aScale, WGR::OutputVertex* aBuffer, size_t aBufferCapacity) { 4745 AAStroke::StrokeStyle style = {aStrokeOptions->mLineWidth * aScale, 4746 ToAAStrokeLineCap(aStrokeOptions->mLineCap), 4747 ToAAStrokeLineJoin(aStrokeOptions->mLineJoin), 4748 aStrokeOptions->mMiterLimit}; 4749 if (style.width <= 0.0f || !std::isfinite(style.width) || 4750 style.miter_limit <= 0.0f || !std::isfinite(style.miter_limit)) { 4751 return Nothing(); 4752 } 4753 AAStroke::Stroker* s = AAStroke::aa_stroke_new( 4754 &style, (AAStroke::OutputVertex*)aBuffer, aBufferCapacity); 4755 bool valid = true; 4756 size_t curPoint = 0; 4757 for (size_t curType = 0; valid && curType < aPath.mPath.num_types;) { 4758 // Verify that we are at the start of a sub-path. 4759 if ((aPath.mPath.types[curType] & WGR::PathPointTypePathTypeMask) != 4760 WGR::PathPointTypeStart) { 4761 valid = false; 4762 break; 4763 } 4764 // Find where the next sub-path starts so we can locate the end. 4765 size_t endType = curType + 1; 4766 for (; endType < aPath.mPath.num_types; endType++) { 4767 if ((aPath.mPath.types[endType] & WGR::PathPointTypePathTypeMask) == 4768 WGR::PathPointTypeStart) { 4769 break; 4770 } 4771 } 4772 // Check if the path is closed. This is a flag modifying the last type. 4773 bool closed = 4774 (aPath.mPath.types[endType - 1] & WGR::PathPointTypeCloseSubpath) != 0; 4775 for (; curType < endType; curType++) { 4776 // If this is the last type and the sub-path is not closed, determine if 4777 // this segment should be capped. 4778 bool end = curType + 1 == endType && !closed; 4779 switch (aPath.mPath.types[curType] & WGR::PathPointTypePathTypeMask) { 4780 case WGR::PathPointTypeStart: { 4781 if (curPoint + 1 > aPath.mPath.num_points) { 4782 valid = false; 4783 break; 4784 } 4785 Point p1 = WGRPointToPoint(aPath.mPath.points[curPoint]); 4786 AAStroke::aa_stroke_move_to(s, p1.x, p1.y, closed); 4787 if (end) { 4788 AAStroke::aa_stroke_line_to(s, p1.x, p1.y, true); 4789 } 4790 curPoint++; 4791 break; 4792 } 4793 case WGR::PathPointTypeLine: { 4794 if (curPoint + 1 > aPath.mPath.num_points) { 4795 valid = false; 4796 break; 4797 } 4798 Point p1 = WGRPointToPoint(aPath.mPath.points[curPoint]); 4799 AAStroke::aa_stroke_line_to(s, p1.x, p1.y, end); 4800 curPoint++; 4801 break; 4802 } 4803 case WGR::PathPointTypeBezier: { 4804 if (curPoint + 3 > aPath.mPath.num_points) { 4805 valid = false; 4806 break; 4807 } 4808 Point p1 = WGRPointToPoint(aPath.mPath.points[curPoint]); 4809 Point p2 = WGRPointToPoint(aPath.mPath.points[curPoint + 1]); 4810 Point p3 = WGRPointToPoint(aPath.mPath.points[curPoint + 2]); 4811 AAStroke::aa_stroke_curve_to(s, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, 4812 end); 4813 curPoint += 3; 4814 break; 4815 } 4816 default: 4817 MOZ_ASSERT(false, "Unknown WGR path point type"); 4818 valid = false; 4819 break; 4820 } 4821 } 4822 // Close the sub-path if necessary. 4823 if (valid && closed) { 4824 AAStroke::aa_stroke_close(s); 4825 } 4826 } 4827 Maybe<AAStroke::VertexBuffer> result; 4828 if (valid) { 4829 AAStroke::VertexBuffer vb = AAStroke::aa_stroke_finish(s); 4830 if (!vb.len || (aBuffer && vb.len > aBufferCapacity)) { 4831 AAStroke::aa_stroke_vertex_buffer_release(vb); 4832 } else { 4833 result = Some(vb); 4834 } 4835 } 4836 AAStroke::aa_stroke_release(s); 4837 return result; 4838 } 4839 4840 // Search the path cache for any entries stored in the path vertex buffer and 4841 // remove them. 4842 void PathCache::ClearVertexRanges() { 4843 for (auto& chain : mChains) { 4844 PathCacheEntry* entry = chain.getFirst(); 4845 while (entry) { 4846 PathCacheEntry* next = entry->getNext(); 4847 if (entry->GetVertexRange().IsValid()) { 4848 entry->Unlink(); 4849 } 4850 entry = next; 4851 } 4852 } 4853 } 4854 4855 inline bool DrawTargetWebgl::ShouldAccelPath( 4856 const DrawOptions& aOptions, const StrokeOptions* aStrokeOptions, 4857 const Rect& aRect) { 4858 return mWebglValid && SupportsDrawOptions(aOptions, aRect) && 4859 PrepareContext(); 4860 } 4861 4862 // For now, we only directly support stroking solid color patterns to limit 4863 // artifacts from blending of overlapping geometry generated by AAStroke. Other 4864 // types of patterns may be partially supported by rendering to a temporary 4865 // mask. 4866 static inline AAStrokeMode SupportsAAStroke(const Pattern& aPattern, 4867 const DrawOptions& aOptions, 4868 const StrokeOptions& aStrokeOptions, 4869 bool aAllowStrokeAlpha) { 4870 if (aStrokeOptions.mDashPattern) { 4871 return AAStrokeMode::Unsupported; 4872 } 4873 switch (aOptions.mCompositionOp) { 4874 case CompositionOp::OP_SOURCE: 4875 return AAStrokeMode::Geometry; 4876 case CompositionOp::OP_OVER: 4877 if (aPattern.GetType() == PatternType::COLOR) { 4878 return static_cast<const ColorPattern&>(aPattern).mColor.a * 4879 aOptions.mAlpha < 4880 1.0f && 4881 !aAllowStrokeAlpha 4882 ? AAStrokeMode::Mask 4883 : AAStrokeMode::Geometry; 4884 } 4885 return AAStrokeMode::Unsupported; 4886 default: 4887 return AAStrokeMode::Unsupported; 4888 } 4889 } 4890 4891 // Render an AA-Stroke'd vertex range into an R8 mask texture for subsequent 4892 // drawing. 4893 already_AddRefed<TextureHandle> SharedContextWebgl::DrawStrokeMask( 4894 const PathVertexRange& aVertexRange, const IntSize& aSize) { 4895 // Allocate a new texture handle to store the rendered mask. 4896 RefPtr<TextureHandle> handle = 4897 AllocateTextureHandle(SurfaceFormat::A8, aSize, true, true); 4898 if (!handle) { 4899 return nullptr; 4900 } 4901 4902 IntRect texBounds = handle->GetBounds(); 4903 BindScratchFramebuffer(handle, true); 4904 4905 // Reset any blending when drawing the mask. 4906 SetBlendState(CompositionOp::OP_OVER); 4907 4908 // Set up the solid color shader to draw a simple opaque mask. 4909 if (mLastProgram != mSolidProgram) { 4910 mWebgl->UseProgram(mSolidProgram); 4911 mLastProgram = mSolidProgram; 4912 } 4913 Array<float, 2> viewportData = {float(texBounds.width), 4914 float(texBounds.height)}; 4915 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramViewport, viewportData, 4916 mSolidProgramUniformState.mViewport); 4917 Array<float, 1> aaData = {0.0f}; 4918 MaybeUniformData(LOCAL_GL_FLOAT, mSolidProgramAA, aaData, 4919 mSolidProgramUniformState.mAA); 4920 Array<float, 4> clipData = {-0.5f, -0.5f, float(texBounds.width) + 0.5f, 4921 float(texBounds.height) + 0.5f}; 4922 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramClipBounds, clipData, 4923 mSolidProgramUniformState.mClipBounds); 4924 Array<float, 4> colorData = {1.0f, 1.0f, 1.0f, 1.0f}; 4925 MaybeUniformData(LOCAL_GL_FLOAT_VEC4, mSolidProgramColor, colorData, 4926 mSolidProgramUniformState.mColor); 4927 Array<float, 6> xformData = {1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}; 4928 MaybeUniformData(LOCAL_GL_FLOAT_VEC2, mSolidProgramTransform, xformData, 4929 mSolidProgramUniformState.mTransform); 4930 4931 // Ensure the current clip mask is ignored. 4932 RefPtr<WebGLTexture> prevClipMask = mLastClipMask; 4933 SetNoClipMask(); 4934 4935 // Draw the mask using the supplied path vertex range. 4936 DrawTriangles(aVertexRange); 4937 4938 // Restore the previous framebuffer state. 4939 RestoreCurrentTarget(prevClipMask); 4940 4941 return handle.forget(); 4942 } 4943 4944 // Attempts to draw a path using WGR (or AAStroke), when possible. 4945 bool SharedContextWebgl::DrawWGRPath( 4946 const Path* aPath, const IntRect& aIntBounds, const Rect& aQuantBounds, 4947 const Matrix& aPathXform, RefPtr<PathCacheEntry>& aEntry, 4948 const DrawOptions& aOptions, const StrokeOptions* aStrokeOptions, 4949 AAStrokeMode aAAStrokeMode, const Pattern& aPattern, 4950 const Maybe<DeviceColor>& aColor) { 4951 const PathSkia* pathSkia = static_cast<const PathSkia*>(aPath); 4952 const Matrix& currentTransform = mCurrentTarget->GetTransform(); 4953 if (aEntry->GetVertexRange().IsValid()) { 4954 // If there is a valid cached vertex data in the path vertex buffer, then 4955 // just draw that. We must draw at integer pixel boundaries (using 4956 // intBounds instead of quantBounds) due to WGR's reliance on pixel center 4957 // location. 4958 mCurrentTarget->mProfile.OnCacheHit(); 4959 return DrawRectAccel(Rect(aIntBounds.TopLeft(), Size(1, 1)), aPattern, 4960 aOptions, Nothing(), nullptr, false, true, true, false, 4961 nullptr, &aEntry->GetVertexRange()); 4962 } 4963 4964 // printf_stderr("Generating... verbs %d, points %d\n", 4965 // int(pathSkia->GetPath().countVerbs()), 4966 // int(pathSkia->GetPath().countPoints())); 4967 WGR::OutputVertex* outputBuffer = nullptr; 4968 size_t outputBufferCapacity = 0; 4969 if (mWGROutputBuffer) { 4970 outputBuffer = mWGROutputBuffer.get(); 4971 outputBufferCapacity = mPathVertexCapacity / sizeof(WGR::OutputVertex); 4972 } 4973 Maybe<WGR::VertexBuffer> wgrVB; 4974 Maybe<AAStroke::VertexBuffer> strokeVB; 4975 if (!aStrokeOptions) { 4976 if (aPath == mUnitCirclePath) { 4977 auto scaleFactors = aPathXform.ScaleFactors(); 4978 if (scaleFactors.AreScalesSame()) { 4979 Point center = aPathXform.GetTranslation() - aQuantBounds.TopLeft(); 4980 float radius = scaleFactors.xScale; 4981 AAStroke::VertexBuffer vb = AAStroke::aa_stroke_filled_circle( 4982 center.x, center.y, radius, (AAStroke::OutputVertex*)outputBuffer, 4983 outputBufferCapacity); 4984 if (!vb.len || (outputBuffer && vb.len > outputBufferCapacity)) { 4985 AAStroke::aa_stroke_vertex_buffer_release(vb); 4986 } else { 4987 strokeVB = Some(vb); 4988 } 4989 } 4990 } 4991 if (!strokeVB) { 4992 wgrVB = GeneratePathVertexBuffer( 4993 aEntry->GetPath(), IntRect(-aIntBounds.TopLeft(), mViewportSize), 4994 mRasterizationTruncates, outputBuffer, outputBufferCapacity); 4995 } 4996 } else { 4997 if (aAAStrokeMode != AAStrokeMode::Unsupported) { 4998 auto scaleFactors = currentTransform.ScaleFactors(); 4999 if (scaleFactors.AreScalesSame()) { 5000 strokeVB = GenerateStrokeVertexBuffer(aEntry->GetPath(), aStrokeOptions, 5001 scaleFactors.xScale, outputBuffer, 5002 outputBufferCapacity); 5003 } 5004 } 5005 if (!strokeVB && mPathWGRStroke) { 5006 // If stroking, then generate a path to fill the stroked region. This 5007 // path will need to be quantized again because it differs from the 5008 // path used for the cache entry, but this allows us to avoid 5009 // generating a fill path on a cache hit. 5010 Maybe<Rect> cullRect; 5011 Matrix invTransform = currentTransform; 5012 if (invTransform.Invert()) { 5013 // Transform the stroking clip rect from device space to local 5014 // space. 5015 Rect invRect = invTransform.TransformBounds(Rect(mClipRect)); 5016 invRect.RoundOut(); 5017 cullRect = Some(invRect); 5018 } 5019 SkPath fillPath; 5020 if (pathSkia->GetFillPath(*aStrokeOptions, aPathXform, fillPath, 5021 cullRect)) { 5022 // printf_stderr(" stroke fill... verbs %d, points %d\n", 5023 // int(fillPath.countVerbs()), 5024 // int(fillPath.countPoints())); 5025 if (Maybe<QuantizedPath> qp = GenerateQuantizedPath( 5026 mWGRPathBuilder, fillPath, aQuantBounds, aPathXform)) { 5027 wgrVB = GeneratePathVertexBuffer( 5028 *qp, IntRect(-aIntBounds.TopLeft(), mViewportSize), 5029 mRasterizationTruncates, outputBuffer, outputBufferCapacity); 5030 } 5031 } 5032 } 5033 } 5034 if (!wgrVB && !strokeVB) { 5035 // Failed to generate any vertex data. 5036 return false; 5037 } 5038 const uint8_t* vbData = 5039 wgrVB ? (const uint8_t*)wgrVB->data : (const uint8_t*)strokeVB->data; 5040 if (outputBuffer && !vbData) { 5041 vbData = (const uint8_t*)outputBuffer; 5042 } 5043 size_t vbLen = wgrVB ? wgrVB->len : strokeVB->len; 5044 uint32_t vertexBytes = 5045 uint32_t(std::min(vbLen * sizeof(WGR::OutputVertex), size_t(UINT32_MAX))); 5046 // printf_stderr(" ... %d verts, %d bytes\n", int(vbLen), 5047 // int(vertexBytes)); 5048 if (vertexBytes > mPathVertexCapacity - mPathVertexOffset && 5049 vertexBytes <= mPathVertexCapacity - sizeof(kRectVertexData)) { 5050 // If the vertex data is too large to fit in the remaining path vertex 5051 // buffer, then orphan the contents of the vertex buffer to make room 5052 // for it. 5053 if (mPathCache) { 5054 mPathCache->ClearVertexRanges(); 5055 } 5056 ResetPathVertexBuffer(); 5057 } 5058 if (vertexBytes > mPathVertexCapacity - mPathVertexOffset) { 5059 // There is insufficient space in the path buffer to fit vertex data. 5060 if (wgrVB) { 5061 WGR::wgr_vertex_buffer_release(wgrVB.ref()); 5062 } else { 5063 AAStroke::aa_stroke_vertex_buffer_release(strokeVB.ref()); 5064 } 5065 return false; 5066 } 5067 // If there is actually room to fit the vertex data in the vertex buffer 5068 // after orphaning as necessary, then upload the data to the next 5069 // available offset in the buffer. 5070 PathVertexRange vertexRange( 5071 uint32_t(mPathVertexOffset / sizeof(WGR::OutputVertex)), uint32_t(vbLen)); 5072 // printf_stderr(" ... offset %d\n", mPathVertexOffset); 5073 // Normal glBufferSubData interleaved with draw calls causes performance 5074 // issues on Mali, so use our special unsynchronized version. This is 5075 // safe as we never update regions referenced by pending draw calls. 5076 mWebgl->BufferSubData(LOCAL_GL_ARRAY_BUFFER, mPathVertexOffset, vertexBytes, 5077 vbData, 5078 /* unsynchronized */ true); 5079 mPathVertexOffset += vertexBytes; 5080 if (wgrVB) { 5081 WGR::wgr_vertex_buffer_release(wgrVB.ref()); 5082 } else { 5083 AAStroke::aa_stroke_vertex_buffer_release(strokeVB.ref()); 5084 } 5085 if (strokeVB && aAAStrokeMode == AAStrokeMode::Mask) { 5086 // Attempt to generate a stroke mask for path. 5087 if (RefPtr<TextureHandle> handle = 5088 DrawStrokeMask(vertexRange, aIntBounds.Size())) { 5089 // Finally, draw the rendered stroke mask. 5090 if (aEntry) { 5091 aEntry->Link(handle); 5092 } 5093 mCurrentTarget->mProfile.OnCacheMiss(); 5094 SurfacePattern maskPattern(nullptr, ExtendMode::CLAMP, 5095 Matrix::Translation(aQuantBounds.TopLeft()), 5096 SamplingFilter::GOOD); 5097 return DrawRectAccel(aQuantBounds, maskPattern, aOptions, aColor, &handle, 5098 false, true, true); 5099 } 5100 } else { 5101 // Remember the vertex range in the cache entry so that it can be 5102 // reused later. 5103 if (aEntry) { 5104 aEntry->SetVertexRange(vertexRange); 5105 } 5106 5107 // Finally, draw the uploaded vertex data. 5108 mCurrentTarget->mProfile.OnCacheMiss(); 5109 return DrawRectAccel(Rect(aIntBounds.TopLeft(), Size(1, 1)), aPattern, 5110 aOptions, Nothing(), nullptr, false, true, true, false, 5111 nullptr, &vertexRange); 5112 } 5113 // If we failed to draw the vertex data for some reason, then fall back 5114 // to the texture rasterization path. 5115 return false; 5116 } 5117 5118 bool SharedContextWebgl::DrawPathAccel( 5119 const Path* aPath, const Pattern& aPattern, const DrawOptions& aOptions, 5120 const StrokeOptions* aStrokeOptions, bool aAllowStrokeAlpha, 5121 const ShadowOptions* aShadow, bool aCacheable, const Matrix* aPathXform) { 5122 // Get the transformed bounds for the path and conservatively check if the 5123 // bounds overlap the canvas. 5124 const PathSkia* pathSkia = static_cast<const PathSkia*>(aPath); 5125 const Matrix& currentTransform = mCurrentTarget->GetTransform(); 5126 Matrix pathXform = currentTransform; 5127 // If there is a path-specific transform that shouldn't be applied to the 5128 // pattern, then generate a matrix that should only be used with the Skia 5129 // path. 5130 if (aPathXform) { 5131 pathXform.PreMultiply(*aPathXform); 5132 } 5133 Rect bounds = pathSkia->GetFastBounds(pathXform, aStrokeOptions); 5134 // If the path is empty, then there is nothing to draw. 5135 if (bounds.IsEmpty()) { 5136 return true; 5137 } 5138 // Avoid integer conversion errors with abnormally large paths. 5139 if (!RectInsidePrecisionLimits(bounds)) { 5140 return false; 5141 } 5142 IntRect viewport(IntPoint(), mViewportSize); 5143 bool accelShadow = false; 5144 if (aShadow) { 5145 // Inflate the bounds to account for the blur radius. 5146 bounds += aShadow->mOffset; 5147 if (aShadow->mSigma > 0.0f && aShadow->mSigma <= BLUR_ACCEL_SIGMA_MAX && 5148 !RequiresMultiStageBlend(aOptions)) { 5149 // Allow the input texture to be reused regardless of sigma since it 5150 // doesn't actually differ. 5151 viewport.Inflate(2 * BLUR_ACCEL_RADIUS_MAX); 5152 accelShadow = true; 5153 } else { 5154 // Software blurs require inflated inputs dependent on blur radius. 5155 int32_t blurRadius = aShadow->BlurRadius(); 5156 bounds.Inflate(blurRadius); 5157 viewport.Inflate(blurRadius); 5158 } 5159 } 5160 Point realOrigin = bounds.TopLeft(); 5161 if (aCacheable) { 5162 // Quantize the path origin to increase the reuse of cache entries. 5163 bounds.Scale(4.0f); 5164 bounds.Round(); 5165 bounds.Scale(0.25f); 5166 } 5167 Point quantizedOrigin = bounds.TopLeft(); 5168 // If the path doesn't intersect the viewport, then there is nothing to draw. 5169 IntRect intBounds = RoundedOut(bounds).Intersect(viewport); 5170 if (intBounds.IsEmpty()) { 5171 return true; 5172 } 5173 // Nudge the bounds to account for the quantization rounding. 5174 Rect quantBounds = Rect(intBounds) + (realOrigin - quantizedOrigin); 5175 // If the pattern is a solid color, then this will be used along with a path 5176 // mask to render the path, as opposed to baking the pattern into the cached 5177 // path texture. 5178 Maybe<DeviceColor> color = 5179 aOptions.mCompositionOp == CompositionOp::OP_CLEAR 5180 ? Some(DeviceColor(1, 1, 1, 1)) 5181 : (aPattern.GetType() == PatternType::COLOR 5182 ? Some(static_cast<const ColorPattern&>(aPattern).mColor) 5183 : Nothing()); 5184 AAStrokeMode aaStrokeMode = 5185 aStrokeOptions && mPathAAStroke 5186 ? SupportsAAStroke(aPattern, aOptions, *aStrokeOptions, 5187 aAllowStrokeAlpha) 5188 : AAStrokeMode::Unsupported; 5189 // Look for an existing path cache entry, if possible, or otherwise create 5190 // one. If the draw request is not cacheable, then don't create an entry. 5191 RefPtr<PathCacheEntry> entry; 5192 RefPtr<TextureHandle> handle; 5193 if (aCacheable) { 5194 if (!mPathCache) { 5195 mPathCache = MakeUnique<PathCache>(); 5196 } 5197 // Use a quantized, relative (to its bounds origin) version of the path as 5198 // a cache key to help limit cache bloat. 5199 Maybe<QuantizedPath> qp = GenerateQuantizedPath( 5200 mWGRPathBuilder, pathSkia->GetPath(), quantBounds, pathXform); 5201 if (!qp) { 5202 return false; 5203 } 5204 entry = mPathCache->FindOrInsertEntry( 5205 std::move(*qp), color ? nullptr : &aPattern, aStrokeOptions, 5206 aaStrokeMode, currentTransform, intBounds, quantizedOrigin, 5207 aShadow ? aShadow->mSigma : -1.0f); 5208 if (!entry) { 5209 return false; 5210 } 5211 handle = entry->GetHandle(); 5212 } 5213 5214 // If there is a shadow, it needs to draw with the shadow color rather than 5215 // the path color. 5216 Maybe<DeviceColor> shadowColor = color; 5217 if (aShadow && aOptions.mCompositionOp != CompositionOp::OP_CLEAR) { 5218 shadowColor = Some(aShadow->mColor); 5219 if (color) { 5220 shadowColor->a *= color->a; 5221 } 5222 } 5223 SamplingFilter filter = 5224 aShadow ? SamplingFilter::GOOD : GetSamplingFilter(aPattern); 5225 if (handle && handle->IsValid()) { 5226 if (accelShadow) { 5227 return BlurRectAccel(quantBounds, Point(aShadow->mSigma, aShadow->mSigma), 5228 nullptr, IntRect(), aOptions, shadowColor, nullptr, 5229 &handle); 5230 } 5231 5232 // If the entry has a valid texture handle still, use it. However, the 5233 // entry texture is assumed to be located relative to its previous bounds. 5234 // We need to offset the pattern by the difference between its new unclipped 5235 // origin and its previous previous unclipped origin. Then when we finally 5236 // draw a rectangle at the expected new bounds, it will overlap the portion 5237 // of the old entry texture we actually need to sample from. 5238 Point offset = 5239 (realOrigin - entry->GetOrigin()) + entry->GetBounds().TopLeft(); 5240 SurfacePattern pathPattern(nullptr, ExtendMode::CLAMP, 5241 Matrix::Translation(offset), filter); 5242 return DrawRectAccel(quantBounds, pathPattern, aOptions, shadowColor, 5243 &handle, false, true, true); 5244 } 5245 5246 if (mPathVertexCapacity > 0 && !handle && entry && !aShadow && 5247 aOptions.mAntialiasMode != AntialiasMode::NONE && 5248 entry->GetPath().mPath.num_types <= mPathMaxComplexity) { 5249 if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) { 5250 if (Maybe<SurfacePattern> gradient = 5251 mCurrentTarget->LinearGradientToSurface(WidenToDouble(bounds), 5252 aPattern)) { 5253 if (DrawWGRPath(aPath, intBounds, quantBounds, pathXform, entry, 5254 aOptions, aStrokeOptions, aaStrokeMode, gradient.ref(), 5255 color)) { 5256 return true; 5257 } 5258 } 5259 } else if (SupportsPattern(aPattern) && 5260 DrawWGRPath(aPath, intBounds, quantBounds, pathXform, entry, 5261 aOptions, aStrokeOptions, aaStrokeMode, aPattern, 5262 color)) { 5263 return true; 5264 } 5265 } 5266 5267 // If a stroke path covers too much screen area, it is likely that most is 5268 // empty space in the interior. This usually imposes too high a cost versus 5269 // just rasterizing without acceleration. Note that AA-Stroke generally 5270 // produces more acceptable amounts of geometry for larger paths, so we do 5271 // this heuristic after we attempt AA-Stroke. 5272 if (aStrokeOptions && 5273 intBounds.width * intBounds.height > 5274 (mViewportSize.width / 2) * (mViewportSize.height / 2)) { 5275 // Avoid filling cache with empty entries. 5276 if (entry) { 5277 entry->Unlink(); 5278 } 5279 return false; 5280 } 5281 5282 // If there is a similar shadow entry with a different blur radius that still 5283 // has a valid input texture cached. The blurred texture can be generated from 5284 // the previously cached input texture without incurring an upload cost. 5285 if (accelShadow && entry) { 5286 if (RefPtr<PathCacheEntry> similarEntry = mPathCache->FindEntry( 5287 entry->GetPath(), color ? nullptr : &aPattern, aStrokeOptions, 5288 aaStrokeMode, currentTransform, intBounds, quantizedOrigin, 5289 kIgnoreSigma, true)) { 5290 if (RefPtr<TextureHandle> inputHandle = 5291 similarEntry->GetSecondaryHandle().get()) { 5292 if (inputHandle->IsValid() && 5293 BlurRectAccel(quantBounds, Point(aShadow->mSigma, aShadow->mSigma), 5294 nullptr, IntRect(), aOptions, shadowColor, 5295 &inputHandle, &handle)) { 5296 if (entry) { 5297 entry->Link(handle); 5298 entry->SetSecondaryHandle(WeakPtr(inputHandle)); 5299 } 5300 return true; 5301 } 5302 } 5303 } 5304 } 5305 5306 // If there isn't a valid texture handle, then we need to rasterize the 5307 // path in a software canvas and upload this to a texture. Solid color 5308 // patterns will be rendered as a path mask that can then be modulated 5309 // with any color. Other pattern types have to rasterize the pattern 5310 // directly into the cached texture. 5311 handle = nullptr; 5312 RefPtr<DrawTargetSkia> pathDT = new DrawTargetSkia; 5313 if (pathDT->Init(intBounds.Size(), color || aShadow 5314 ? SurfaceFormat::A8 5315 : SurfaceFormat::B8G8R8A8)) { 5316 Point offset = -quantBounds.TopLeft(); 5317 if (aShadow) { 5318 // Ensure the the shadow is drawn at the requested offset 5319 offset += aShadow->mOffset; 5320 } 5321 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, 5322 aOptions.mAntialiasMode); 5323 static const ColorPattern maskPattern(DeviceColor(1.0f, 1.0f, 1.0f, 1.0f)); 5324 const Pattern& cachePattern = color ? maskPattern : aPattern; 5325 // If the source pattern is a DrawTargetWebgl snapshot, we may shift 5326 // targets when drawing the path, so back up the old target. 5327 DrawTargetWebgl* oldTarget = mCurrentTarget; 5328 RefPtr<TextureHandle> oldHandle = mTargetHandle; 5329 IntSize oldViewport = mViewportSize; 5330 { 5331 RefPtr<const Path> path; 5332 if (!aPathXform || (color && !aStrokeOptions)) { 5333 // If the pattern is transform invariant or there is no pathXform, then 5334 // it is safe to use the path directly. Solid colors are transform 5335 // invariant, except when there are stroke options such as line width or 5336 // dashes that should not be scaled by pathXform. 5337 path = aPath; 5338 pathDT->SetTransform(pathXform * Matrix::Translation(offset)); 5339 } else { 5340 // If there is a pathXform, then pre-apply that to the path to avoid 5341 // altering the pattern. 5342 RefPtr<PathBuilder> builder = 5343 aPath->TransformedCopyToBuilder(*aPathXform); 5344 path = builder->Finish(); 5345 pathDT->SetTransform(currentTransform * Matrix::Translation(offset)); 5346 } 5347 if (aStrokeOptions) { 5348 pathDT->Stroke(path, cachePattern, *aStrokeOptions, drawOptions); 5349 } else { 5350 pathDT->Fill(path, cachePattern, drawOptions); 5351 } 5352 } 5353 if (aShadow && aShadow->mSigma > 0.0f) { 5354 if (accelShadow) { 5355 RefPtr<SourceSurface> pathSurface = pathDT->Snapshot(); 5356 // If the target changed, try to restore it. 5357 if ((mCurrentTarget == oldTarget && mTargetHandle == oldHandle && 5358 mViewportSize == oldViewport) || 5359 oldTarget->PrepareContext(!oldHandle, oldHandle, oldViewport)) { 5360 RefPtr<TextureHandle> inputHandle; 5361 // Generate the accelerated shadow from the software surface. 5362 if (BlurRectAccel(quantBounds, 5363 Point(aShadow->mSigma, aShadow->mSigma), 5364 pathSurface, IntRect(), aOptions, shadowColor, 5365 &inputHandle, &handle)) { 5366 if (entry) { 5367 entry->Link(handle); 5368 entry->SetSecondaryHandle(WeakPtr(inputHandle)); 5369 } 5370 } else if (entry) { 5371 entry->Unlink(); 5372 } 5373 return true; 5374 } 5375 return false; 5376 } 5377 // Blur the shadow if required. 5378 GaussianBlur blur(Point(aShadow->mSigma, aShadow->mSigma)); 5379 pathDT->Blur(blur); 5380 } 5381 RefPtr<SourceSurface> pathSurface = pathDT->Snapshot(); 5382 // If the target changed, try to restore it. 5383 if (pathSurface && 5384 ((mCurrentTarget == oldTarget && mTargetHandle == oldHandle && 5385 mViewportSize == oldViewport) || 5386 oldTarget->PrepareContext(!oldHandle, oldHandle, oldViewport))) { 5387 SurfacePattern pathPattern(pathSurface, ExtendMode::CLAMP, 5388 Matrix::Translation(quantBounds.TopLeft()), 5389 filter); 5390 // Try and upload the rasterized path to a texture. If there is a 5391 // valid texture handle after this, then link it to the entry. 5392 // Otherwise, we might have to fall back to software drawing the 5393 // path, so unlink it from the entry. 5394 if (DrawRectAccel(quantBounds, pathPattern, aOptions, shadowColor, 5395 &handle, false, true) && 5396 handle) { 5397 if (entry) { 5398 entry->Link(handle); 5399 } 5400 } else if (entry) { 5401 entry->Unlink(); 5402 } 5403 return true; 5404 } 5405 } 5406 5407 // Avoid filling cache with empty entries. 5408 if (entry) { 5409 entry->Unlink(); 5410 } 5411 return false; 5412 } 5413 5414 void DrawTargetWebgl::DrawPath(const Path* aPath, const Pattern& aPattern, 5415 const DrawOptions& aOptions, 5416 const StrokeOptions* aStrokeOptions, 5417 bool aAllowStrokeAlpha) { 5418 // If there is a WebGL context, then try to cache the path to avoid slow 5419 // fallbacks. 5420 if (ShouldAccelPath(aOptions, aStrokeOptions) && 5421 mSharedContext->DrawPathAccel(aPath, aPattern, aOptions, aStrokeOptions, 5422 aAllowStrokeAlpha)) { 5423 return; 5424 } 5425 5426 // There was no path cache entry available to use, so fall back to drawing the 5427 // path with Skia. 5428 MarkSkiaChanged(aOptions); 5429 if (aStrokeOptions) { 5430 mSkia->Stroke(aPath, aPattern, *aStrokeOptions, aOptions); 5431 } else { 5432 mSkia->Fill(aPath, aPattern, aOptions); 5433 } 5434 } 5435 5436 // DrawCircleAccel is a more specialized version of DrawPathAccel that attempts 5437 // to cache a unit circle. 5438 bool SharedContextWebgl::DrawCircleAccel(const Point& aCenter, float aRadius, 5439 const Pattern& aPattern, 5440 const DrawOptions& aOptions, 5441 const StrokeOptions* aStrokeOptions) { 5442 // Cache a unit circle and transform it to avoid creating a path repeatedly. 5443 if (!mUnitCirclePath) { 5444 mUnitCirclePath = MakePathForCircle(*mCurrentTarget, Point(0, 0), 1); 5445 } 5446 // Scale and translate the circle to the desired shape. 5447 Matrix circleXform(aRadius, 0, 0, aRadius, aCenter.x, aCenter.y); 5448 return DrawPathAccel(mUnitCirclePath, aPattern, aOptions, aStrokeOptions, 5449 true, nullptr, true, &circleXform); 5450 } 5451 5452 void DrawTargetWebgl::DrawCircle(const Point& aOrigin, float aRadius, 5453 const Pattern& aPattern, 5454 const DrawOptions& aOptions, 5455 const StrokeOptions* aStrokeOptions) { 5456 if (ShouldAccelPath(aOptions, aStrokeOptions) && 5457 mSharedContext->DrawCircleAccel(aOrigin, aRadius, aPattern, aOptions, 5458 aStrokeOptions)) { 5459 return; 5460 } 5461 5462 MarkSkiaChanged(aOptions); 5463 if (aStrokeOptions) { 5464 mSkia->StrokeCircle(aOrigin, aRadius, aPattern, *aStrokeOptions, aOptions); 5465 } else { 5466 mSkia->FillCircle(aOrigin, aRadius, aPattern, aOptions); 5467 } 5468 } 5469 5470 void DrawTargetWebgl::DrawSurface(SourceSurface* aSurface, const Rect& aDest, 5471 const Rect& aSource, 5472 const DrawSurfaceOptions& aSurfOptions, 5473 const DrawOptions& aOptions) { 5474 Matrix matrix = Matrix::Scaling(aDest.width / aSource.width, 5475 aDest.height / aSource.height); 5476 matrix.PreTranslate(-aSource.TopLeft()); 5477 matrix.PostTranslate(aDest.TopLeft()); 5478 5479 // Ensure the source rect is clipped to the surface bounds. 5480 Rect src = aSource.Intersect(Rect(aSurface->GetRect())); 5481 // Ensure the destination rect does not sample outside the surface bounds. 5482 Rect dest = matrix.TransformBounds(src).Intersect(aDest); 5483 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, matrix, 5484 aSurfOptions.mSamplingFilter); 5485 DrawRect(dest, pattern, aOptions); 5486 } 5487 5488 void DrawTargetWebgl::Mask(const Pattern& aSource, const Pattern& aMask, 5489 const DrawOptions& aOptions) { 5490 if (!SupportsDrawOptions(aOptions) || 5491 aMask.GetType() != PatternType::SURFACE || 5492 aSource.GetType() != PatternType::COLOR) { 5493 MarkSkiaChanged(aOptions); 5494 mSkia->Mask(aSource, aMask, aOptions); 5495 return; 5496 } 5497 auto sourceColor = static_cast<const ColorPattern&>(aSource).mColor; 5498 auto maskPattern = static_cast<const SurfacePattern&>(aMask); 5499 if (!maskPattern.mSurface) { 5500 return; 5501 } 5502 5503 IntRect samplingRect = !maskPattern.mSamplingRect.IsEmpty() 5504 ? maskPattern.mSamplingRect 5505 : maskPattern.mSurface->GetRect(); 5506 DrawRect(maskPattern.mMatrix.TransformBounds(Rect(samplingRect)), maskPattern, 5507 aOptions, Some(sourceColor)); 5508 } 5509 5510 void DrawTargetWebgl::MaskSurface(const Pattern& aSource, SourceSurface* aMask, 5511 Point aOffset, const DrawOptions& aOptions) { 5512 if (!SupportsDrawOptions(aOptions) || 5513 aSource.GetType() != PatternType::COLOR) { 5514 MarkSkiaChanged(aOptions); 5515 mSkia->MaskSurface(aSource, aMask, aOffset, aOptions); 5516 } else { 5517 auto sourceColor = static_cast<const ColorPattern&>(aSource).mColor; 5518 SurfacePattern pattern( 5519 aMask, ExtendMode::CLAMP, 5520 Matrix::Translation(aOffset + aMask->GetRect().TopLeft())); 5521 DrawRect(Rect(aOffset, Size(aMask->GetSize())), pattern, aOptions, 5522 Some(sourceColor)); 5523 } 5524 } 5525 5526 // Extract the surface's alpha values into an A8 surface. 5527 static already_AddRefed<DataSourceSurface> ExtractAlpha(SourceSurface* aSurface, 5528 bool aAllowSubpixelAA) { 5529 RefPtr<DataSourceSurface> surfaceData = aSurface->GetDataSurface(); 5530 if (!surfaceData) { 5531 return nullptr; 5532 } 5533 DataSourceSurface::ScopedMap srcMap(surfaceData, DataSourceSurface::READ); 5534 if (!srcMap.IsMapped()) { 5535 return nullptr; 5536 } 5537 IntSize size = surfaceData->GetSize(); 5538 RefPtr<DataSourceSurface> alpha = 5539 Factory::CreateDataSourceSurface(size, SurfaceFormat::A8, false); 5540 if (!alpha) { 5541 return nullptr; 5542 } 5543 DataSourceSurface::ScopedMap dstMap(alpha, DataSourceSurface::WRITE); 5544 if (!dstMap.IsMapped()) { 5545 return nullptr; 5546 } 5547 // For subpixel masks, ignore the alpha and instead sample one of the color 5548 // channels as if they were alpha. 5549 SwizzleData( 5550 srcMap.GetData(), srcMap.GetStride(), 5551 aAllowSubpixelAA ? SurfaceFormat::A8R8G8B8 : surfaceData->GetFormat(), 5552 dstMap.GetData(), dstMap.GetStride(), SurfaceFormat::A8, size); 5553 return alpha.forget(); 5554 } 5555 5556 void DrawTargetWebgl::DrawShadow(const Path* aPath, const Pattern& aPattern, 5557 const ShadowOptions& aShadow, 5558 const DrawOptions& aOptions, 5559 const StrokeOptions* aStrokeOptions) { 5560 if (!aPath || aPath->GetBackendType() != BackendType::SKIA) { 5561 return; 5562 } 5563 5564 // If there is a WebGL context, then try to cache the path to avoid slow 5565 // fallbacks. 5566 if (ShouldAccelPath(aOptions, aStrokeOptions) && 5567 mSharedContext->DrawPathAccel(aPath, aPattern, aOptions, aStrokeOptions, 5568 false, &aShadow)) { 5569 return; 5570 } 5571 5572 // There was no path cache entry available to use, so fall back to drawing the 5573 // path with Skia. 5574 MarkSkiaChanged(aOptions); 5575 mSkia->DrawShadow(aPath, aPattern, aShadow, aOptions, aStrokeOptions); 5576 } 5577 5578 void DrawTargetWebgl::DrawSurfaceWithShadow(SourceSurface* aSurface, 5579 const Point& aDest, 5580 const ShadowOptions& aShadow, 5581 CompositionOp aOperator) { 5582 DrawOptions options(1.0f, aOperator); 5583 if (ShouldAccelPath(options, nullptr)) { 5584 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, 5585 Matrix::Translation(aDest)); 5586 SkPath skiaPath; 5587 skiaPath.addRect(RectToSkRect(Rect(aSurface->GetRect()) + aDest)); 5588 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING); 5589 AutoRestoreTransform restore(this); 5590 SetTransform(Matrix()); 5591 if (mSharedContext->DrawPathAccel(path, pattern, options, nullptr, false, 5592 &aShadow, false)) { 5593 DrawRect(Rect(aSurface->GetRect()) + aDest, pattern, options); 5594 return; 5595 } 5596 } 5597 5598 MarkSkiaChanged(options); 5599 mSkia->DrawSurfaceWithShadow(aSurface, aDest, aShadow, aOperator); 5600 } 5601 5602 already_AddRefed<PathBuilder> DrawTargetWebgl::CreatePathBuilder( 5603 FillRule aFillRule) const { 5604 return mSkia->CreatePathBuilder(aFillRule); 5605 } 5606 5607 void DrawTargetWebgl::SetTransform(const Matrix& aTransform) { 5608 DrawTarget::SetTransform(aTransform); 5609 mSkia->SetTransform(aTransform); 5610 } 5611 5612 void DrawTargetWebgl::StrokeRect(const Rect& aRect, const Pattern& aPattern, 5613 const StrokeOptions& aStrokeOptions, 5614 const DrawOptions& aOptions) { 5615 if (!mWebglValid) { 5616 MarkSkiaChanged(aOptions); 5617 mSkia->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions); 5618 } else { 5619 // If the stroke options are unsupported, then transform the rect to a path 5620 // so it can be cached. 5621 SkPath skiaPath; 5622 skiaPath.addRect(RectToSkRect(aRect)); 5623 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING); 5624 DrawPath(path, aPattern, aOptions, &aStrokeOptions, true); 5625 } 5626 } 5627 5628 static inline bool IsThinLine(const Matrix& aTransform, 5629 const StrokeOptions& aStrokeOptions) { 5630 auto scale = aTransform.ScaleFactors(); 5631 return std::max(scale.xScale, scale.yScale) * aStrokeOptions.mLineWidth <= 1; 5632 } 5633 5634 bool DrawTargetWebgl::StrokeLineAccel(const Point& aStart, const Point& aEnd, 5635 const Pattern& aPattern, 5636 const StrokeOptions& aStrokeOptions, 5637 const DrawOptions& aOptions, 5638 bool aClosed) { 5639 // Approximating a wide line as a rectangle works only with certain cap styles 5640 // in the general case (butt or square). However, if the line width is 5641 // sufficiently thin, we can either ignore the round cap (or treat it like 5642 // square for zero-length lines) without causing objectionable artifacts. 5643 // Lines may sometimes be used in closed paths that immediately reverse back, 5644 // in which case we need to use mLineJoin instead of mLineCap to determine the 5645 // actual cap used. 5646 CapStyle capStyle = 5647 aClosed ? (aStrokeOptions.mLineJoin == JoinStyle::ROUND ? CapStyle::ROUND 5648 : CapStyle::BUTT) 5649 : aStrokeOptions.mLineCap; 5650 if (mWebglValid && SupportsPattern(aPattern) && 5651 (capStyle != CapStyle::ROUND || 5652 IsThinLine(GetTransform(), aStrokeOptions)) && 5653 aStrokeOptions.mDashPattern == nullptr && aStrokeOptions.mLineWidth > 0) { 5654 // Treat the line as a rectangle whose center-line is the supplied line and 5655 // for which the height is the supplied line width. Generate a matrix that 5656 // maps the X axis to the orientation of the line and the Y axis to the 5657 // normal vector to the line. This only works if the line caps are squared, 5658 // as rounded rectangles are currently not supported for round line caps. 5659 Point start = aStart; 5660 Point dirX = aEnd - aStart; 5661 Point dirY; 5662 float dirLen = dirX.Length(); 5663 float scale = aStrokeOptions.mLineWidth; 5664 if (dirLen == 0.0f) { 5665 // If the line is zero-length, then only a cap is rendered. 5666 switch (capStyle) { 5667 case CapStyle::BUTT: 5668 // The cap doesn't extend beyond the line so nothing is drawn. 5669 return true; 5670 case CapStyle::ROUND: 5671 case CapStyle::SQUARE: 5672 // Draw a unit square centered at the single point. 5673 dirX = Point(scale, 0.0f); 5674 dirY = Point(0.0f, scale); 5675 // Offset the start by half a unit. 5676 start.x -= 0.5f * scale; 5677 break; 5678 } 5679 } else { 5680 // Make the scale map to a single unit length. 5681 scale /= dirLen; 5682 dirY = Point(-dirX.y, dirX.x) * scale; 5683 if (capStyle == CapStyle::SQUARE) { 5684 // Offset the start by half a unit. 5685 start -= (dirX * scale) * 0.5f; 5686 // Ensure the extent also accounts for the start and end cap. 5687 dirX += dirX * scale; 5688 } 5689 } 5690 Matrix lineXform(dirX.x, dirX.y, dirY.x, dirY.y, start.x - 0.5f * dirY.x, 5691 start.y - 0.5f * dirY.y); 5692 if (PrepareContext() && 5693 mSharedContext->DrawRectAccel(Rect(0, 0, 1, 1), aPattern, aOptions, 5694 Nothing(), nullptr, true, true, true, 5695 false, nullptr, nullptr, &lineXform)) { 5696 return true; 5697 } 5698 } 5699 return false; 5700 } 5701 5702 void DrawTargetWebgl::StrokeLine(const Point& aStart, const Point& aEnd, 5703 const Pattern& aPattern, 5704 const StrokeOptions& aStrokeOptions, 5705 const DrawOptions& aOptions) { 5706 if (!mWebglValid) { 5707 MarkSkiaChanged(aOptions); 5708 mSkia->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions); 5709 } else if (!StrokeLineAccel(aStart, aEnd, aPattern, aStrokeOptions, 5710 aOptions)) { 5711 // If the stroke options are unsupported, then transform the line to a path 5712 // so it can be cached. 5713 SkPath skiaPath; 5714 skiaPath.moveTo(PointToSkPoint(aStart)); 5715 skiaPath.lineTo(PointToSkPoint(aEnd)); 5716 RefPtr<PathSkia> path = new PathSkia(skiaPath, FillRule::FILL_WINDING); 5717 DrawPath(path, aPattern, aOptions, &aStrokeOptions, true); 5718 } 5719 } 5720 5721 void DrawTargetWebgl::Stroke(const Path* aPath, const Pattern& aPattern, 5722 const StrokeOptions& aStrokeOptions, 5723 const DrawOptions& aOptions) { 5724 if (!aPath || aPath->GetBackendType() != BackendType::SKIA) { 5725 return; 5726 } 5727 const auto& skiaPath = static_cast<const PathSkia*>(aPath)->GetPath(); 5728 if (!mWebglValid) { 5729 MarkSkiaChanged(aOptions); 5730 mSkia->Stroke(aPath, aPattern, aStrokeOptions, aOptions); 5731 return; 5732 } 5733 5734 // Avoid using Skia's isLine here because some paths erroneously include a 5735 // closePath at the end, causing isLine to not detect the line. In that case 5736 // we just draw a line in reverse right over the original line. 5737 int numVerbs = skiaPath.countVerbs(); 5738 bool allowStrokeAlpha = false; 5739 if (numVerbs >= 2 && numVerbs <= 3) { 5740 uint8_t verbs[3]; 5741 skiaPath.getVerbs({verbs, numVerbs}); 5742 if (verbs[0] == SkPath::kMove_Verb && verbs[1] == SkPath::kLine_Verb && 5743 (numVerbs < 3 || verbs[2] == SkPath::kClose_Verb)) { 5744 bool closed = numVerbs >= 3; 5745 Point start = SkPointToPoint(skiaPath.getPoint(0)); 5746 Point end = SkPointToPoint(skiaPath.getPoint(1)); 5747 if (StrokeLineAccel(start, end, aPattern, aStrokeOptions, aOptions, 5748 closed)) { 5749 if (closed) { 5750 StrokeLineAccel(end, start, aPattern, aStrokeOptions, aOptions, true); 5751 } 5752 return; 5753 } 5754 // If accelerated line drawing failed, just treat it as a path. 5755 allowStrokeAlpha = true; 5756 } 5757 } 5758 5759 DrawPath(aPath, aPattern, aOptions, &aStrokeOptions, allowStrokeAlpha); 5760 } 5761 5762 void DrawTargetWebgl::StrokeCircle(const Point& aOrigin, float aRadius, 5763 const Pattern& aPattern, 5764 const StrokeOptions& aStrokeOptions, 5765 const DrawOptions& aOptions) { 5766 DrawCircle(aOrigin, aRadius, aPattern, aOptions, &aStrokeOptions); 5767 } 5768 5769 bool DrawTargetWebgl::ShouldUseSubpixelAA(ScaledFont* aFont, 5770 const DrawOptions& aOptions) { 5771 AntialiasMode aaMode = aFont->GetDefaultAAMode(); 5772 if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) { 5773 aaMode = aOptions.mAntialiasMode; 5774 } 5775 return GetPermitSubpixelAA() && 5776 (aaMode == AntialiasMode::DEFAULT || 5777 aaMode == AntialiasMode::SUBPIXEL) && 5778 aOptions.mCompositionOp == CompositionOp::OP_OVER; 5779 } 5780 5781 void DrawTargetWebgl::StrokeGlyphs(ScaledFont* aFont, 5782 const GlyphBuffer& aBuffer, 5783 const Pattern& aPattern, 5784 const StrokeOptions& aStrokeOptions, 5785 const DrawOptions& aOptions) { 5786 if (!aFont || !aBuffer.mNumGlyphs) { 5787 return; 5788 } 5789 5790 bool useSubpixelAA = ShouldUseSubpixelAA(aFont, aOptions); 5791 5792 if (mWebglValid && SupportsDrawOptions(aOptions) && 5793 aPattern.GetType() == PatternType::COLOR && PrepareContext() && 5794 mSharedContext->DrawGlyphsAccel(aFont, aBuffer, aPattern, aOptions, 5795 &aStrokeOptions, useSubpixelAA)) { 5796 return; 5797 } 5798 5799 if (useSubpixelAA) { 5800 // Subpixel AA does not support layering because the subpixel masks can't 5801 // blend with the over op. 5802 MarkSkiaChanged(); 5803 } else { 5804 MarkSkiaChanged(aOptions); 5805 } 5806 mSkia->StrokeGlyphs(aFont, aBuffer, aPattern, aStrokeOptions, aOptions); 5807 } 5808 5809 // Depending on whether we enable subpixel position for a given font, Skia may 5810 // round transformed coordinates differently on each axis. By default, text is 5811 // subpixel quantized horizontally and snapped to a whole integer vertical 5812 // baseline. Axis-flip transforms instead snap to horizontal boundaries while 5813 // subpixel quantizing along the vertical. For other types of transforms, Skia 5814 // just applies subpixel quantization to both axes. 5815 // We must duplicate the amount of quantization Skia applies carefully as a 5816 // boundary value such as 0.49 may round to 0.5 with subpixel quantization, 5817 // but if Skia actually snapped it to a whole integer instead, it would round 5818 // down to 0. If a subsequent glyph with offset 0.51 came in, we might 5819 // mistakenly round it down to 0.5, whereas Skia would round it up to 1. Thus 5820 // we would alias 0.49 and 0.51 to the same cache entry, while Skia would 5821 // actually snap the offset to 0 or 1, depending, resulting in mismatched 5822 // hinting. 5823 static inline IntPoint QuantizeScale(ScaledFont* aFont, 5824 const Matrix& aTransform) { 5825 if (!aFont->UseSubpixelPosition()) { 5826 return {1, 1}; 5827 } 5828 if (aTransform._12 == 0) { 5829 // Glyphs are rendered subpixel horizontally, so snap vertically. 5830 return {4, 1}; 5831 } 5832 if (aTransform._11 == 0) { 5833 // Glyphs are rendered subpixel vertically, so snap horizontally. 5834 return {1, 4}; 5835 } 5836 // The transform isn't aligned, so don't snap. 5837 return {4, 4}; 5838 } 5839 5840 // Skia only supports subpixel positioning to the nearest 1/4 fraction. It 5841 // would be wasteful to attempt to cache text runs with positioning that is 5842 // anymore precise than this. To prevent this cache bloat, we quantize the 5843 // transformed glyph positions to the nearest 1/4. The scaling factor for 5844 // the quantization is baked into the transform, so that if subpixel rounding 5845 // is used on a given axis, then the axis will be multiplied by 4 before 5846 // rounding. Since the quantized position is not used for rasterization, the 5847 // transform is safe to modify as such. 5848 static inline IntPoint QuantizePosition(const Matrix& aTransform, 5849 const IntPoint& aOffset, 5850 const Point& aPosition) { 5851 return RoundedToInt(aTransform.TransformPoint(aPosition)) - aOffset; 5852 } 5853 5854 // Get a quantized starting offset for the glyph buffer. We want this offset 5855 // to encapsulate the transform and buffer offset while still preserving the 5856 // relative subpixel positions of the glyphs this offset is subtracted from. 5857 static inline IntPoint QuantizeOffset(const Matrix& aTransform, 5858 const IntPoint& aQuantizeScale, 5859 const GlyphBuffer& aBuffer) { 5860 IntPoint offset = 5861 RoundedToInt(aTransform.TransformPoint(aBuffer.mGlyphs[0].mPosition)); 5862 offset.x.value &= ~(aQuantizeScale.x.value - 1); 5863 offset.y.value &= ~(aQuantizeScale.y.value - 1); 5864 return offset; 5865 } 5866 5867 // Hashes a glyph buffer to a single hash value that can be used for quick 5868 // comparisons. Each glyph position is transformed and quantized before 5869 // hashing. 5870 HashNumber GlyphCacheEntry::HashGlyphs(const GlyphBuffer& aBuffer, 5871 const Matrix& aTransform, 5872 const IntPoint& aQuantizeScale) { 5873 HashNumber hash = 0; 5874 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer); 5875 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) { 5876 const Glyph& glyph = aBuffer.mGlyphs[i]; 5877 hash = AddToHash(hash, glyph.mIndex); 5878 IntPoint pos = QuantizePosition(aTransform, offset, glyph.mPosition); 5879 hash = AddToHash(hash, pos.x); 5880 hash = AddToHash(hash, pos.y); 5881 } 5882 return hash; 5883 } 5884 5885 // Determines if an existing glyph cache entry matches an incoming text run. 5886 inline bool GlyphCacheEntry::MatchesGlyphs( 5887 const GlyphBuffer& aBuffer, const DeviceColor& aColor, 5888 const Matrix& aTransform, const IntPoint& aQuantizeOffset, 5889 const IntPoint& aBoundsOffset, const IntRect& aClipRect, HashNumber aHash, 5890 const StrokeOptions* aStrokeOptions) { 5891 // First check if the hash matches to quickly reject the text run before any 5892 // more expensive checking. If it matches, then check if the color and 5893 // transform are the same. 5894 if (aHash != mHash || aBuffer.mNumGlyphs != mBuffer.mNumGlyphs || 5895 aColor != mColor || !HasMatchingScale(aTransform, mTransform)) { 5896 return false; 5897 } 5898 // Finally check if all glyphs and their quantized positions match. 5899 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) { 5900 const Glyph& dst = mBuffer.mGlyphs[i]; 5901 const Glyph& src = aBuffer.mGlyphs[i]; 5902 if (dst.mIndex != src.mIndex || 5903 dst.mPosition != Point(QuantizePosition(aTransform, aQuantizeOffset, 5904 src.mPosition))) { 5905 return false; 5906 } 5907 } 5908 // Check that stroke options actually match. 5909 if (aStrokeOptions) { 5910 // If stroking, verify that the entry is also stroked with the same options. 5911 if (!(mStrokeOptions && *aStrokeOptions == *mStrokeOptions)) { 5912 return false; 5913 } 5914 } else if (mStrokeOptions) { 5915 // If not stroking, check if the entry is stroked. If so, don't match. 5916 return false; 5917 } 5918 // Verify that the full bounds, once translated and clipped, are equal to the 5919 // clipped bounds. 5920 return (mFullBounds + aBoundsOffset) 5921 .Intersect(aClipRect) 5922 .IsEqualEdges(GetBounds() + aBoundsOffset); 5923 } 5924 5925 GlyphCacheEntry::GlyphCacheEntry(const GlyphBuffer& aBuffer, 5926 const DeviceColor& aColor, 5927 const Matrix& aTransform, 5928 const IntPoint& aQuantizeScale, 5929 const IntRect& aBounds, 5930 const IntRect& aFullBounds, HashNumber aHash, 5931 StoredStrokeOptions* aStrokeOptions) 5932 : CacheEntryImpl<GlyphCacheEntry>(aTransform, aBounds, aHash), 5933 mColor(aColor), 5934 mFullBounds(aFullBounds), 5935 mStrokeOptions(aStrokeOptions) { 5936 // Store a copy of the glyph buffer with positions already quantized for fast 5937 // comparison later. 5938 Glyph* glyphs = new Glyph[aBuffer.mNumGlyphs]; 5939 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer); 5940 // Make the bounds relative to the offset so we can add a new offset later. 5941 IntPoint boundsOffset(offset.x / aQuantizeScale.x, 5942 offset.y / aQuantizeScale.y); 5943 mBounds -= boundsOffset; 5944 mFullBounds -= boundsOffset; 5945 for (size_t i = 0; i < aBuffer.mNumGlyphs; i++) { 5946 Glyph& dst = glyphs[i]; 5947 const Glyph& src = aBuffer.mGlyphs[i]; 5948 dst.mIndex = src.mIndex; 5949 dst.mPosition = Point(QuantizePosition(aTransform, offset, src.mPosition)); 5950 } 5951 mBuffer.mGlyphs = glyphs; 5952 mBuffer.mNumGlyphs = aBuffer.mNumGlyphs; 5953 } 5954 5955 GlyphCacheEntry::~GlyphCacheEntry() { delete[] mBuffer.mGlyphs; } 5956 5957 // Attempt to find a matching entry in the glyph cache. The caller should check 5958 // whether the contained texture handle is valid to determine if it will need to 5959 // render the text run or just reuse the cached texture. 5960 already_AddRefed<GlyphCacheEntry> GlyphCache::FindEntry( 5961 const GlyphBuffer& aBuffer, const DeviceColor& aColor, 5962 const Matrix& aTransform, const IntPoint& aQuantizeScale, 5963 const IntRect& aClipRect, HashNumber aHash, 5964 const StrokeOptions* aStrokeOptions) { 5965 IntPoint offset = QuantizeOffset(aTransform, aQuantizeScale, aBuffer); 5966 IntPoint boundsOffset(offset.x / aQuantizeScale.x, 5967 offset.y / aQuantizeScale.y); 5968 for (const RefPtr<GlyphCacheEntry>& entry : GetChain(aHash)) { 5969 if (entry->MatchesGlyphs(aBuffer, aColor, aTransform, offset, boundsOffset, 5970 aClipRect, aHash, aStrokeOptions)) { 5971 return do_AddRef(entry); 5972 } 5973 } 5974 return nullptr; 5975 } 5976 5977 // Insert a new entry in the glyph cache. 5978 already_AddRefed<GlyphCacheEntry> GlyphCache::InsertEntry( 5979 const GlyphBuffer& aBuffer, const DeviceColor& aColor, 5980 const Matrix& aTransform, const IntPoint& aQuantizeScale, 5981 const IntRect& aBounds, const IntRect& aFullBounds, HashNumber aHash, 5982 const StrokeOptions* aStrokeOptions) { 5983 StoredStrokeOptions* strokeOptions = nullptr; 5984 if (aStrokeOptions) { 5985 strokeOptions = aStrokeOptions->Clone(); 5986 if (!strokeOptions) { 5987 return nullptr; 5988 } 5989 } 5990 RefPtr<GlyphCacheEntry> entry = 5991 new GlyphCacheEntry(aBuffer, aColor, aTransform, aQuantizeScale, aBounds, 5992 aFullBounds, aHash, strokeOptions); 5993 Insert(entry); 5994 return entry.forget(); 5995 } 5996 5997 GlyphCache::GlyphCache(ScaledFont* aFont) : mFont(aFont) {} 5998 5999 static void ReleaseGlyphCache(void* aPtr) { 6000 delete static_cast<GlyphCache*>(aPtr); 6001 } 6002 6003 // Whether all glyphs in the buffer match the last whitespace glyph queried. 6004 bool GlyphCache::IsWhitespace(const GlyphBuffer& aBuffer) const { 6005 if (!mLastWhitespace) { 6006 return false; 6007 } 6008 uint32_t whitespace = *mLastWhitespace; 6009 for (size_t i = 0; i < aBuffer.mNumGlyphs; ++i) { 6010 if (aBuffer.mGlyphs[i].mIndex != whitespace) { 6011 return false; 6012 } 6013 } 6014 return true; 6015 } 6016 6017 // Remember the last whitespace glyph seen. 6018 void GlyphCache::SetLastWhitespace(const GlyphBuffer& aBuffer) { 6019 mLastWhitespace = Some(aBuffer.mGlyphs[0].mIndex); 6020 } 6021 6022 void DrawTargetWebgl::SetPermitSubpixelAA(bool aPermitSubpixelAA) { 6023 DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA); 6024 mSkia->SetPermitSubpixelAA(aPermitSubpixelAA); 6025 } 6026 6027 // Check for any color glyphs contained within a rasterized BGRA8 text result. 6028 static bool CheckForColorGlyphs(const RefPtr<SourceSurface>& aSurface) { 6029 if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) { 6030 return false; 6031 } 6032 RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface(); 6033 if (!dataSurf) { 6034 return true; 6035 } 6036 DataSourceSurface::ScopedMap map(dataSurf, DataSourceSurface::READ); 6037 if (!map.IsMapped()) { 6038 return true; 6039 } 6040 IntSize size = dataSurf->GetSize(); 6041 const uint8_t* data = map.GetData(); 6042 int32_t stride = map.GetStride(); 6043 for (int y = 0; y < size.height; y++) { 6044 const uint32_t* x = (const uint32_t*)data; 6045 const uint32_t* end = x + size.width; 6046 for (; x < end; x++) { 6047 // Verify if all components are the same as for premultiplied grayscale. 6048 uint32_t color = *x; 6049 uint32_t gray = color & 0xFF; 6050 gray |= gray << 8; 6051 gray |= gray << 16; 6052 if (color != gray) return true; 6053 } 6054 data += stride; 6055 } 6056 return false; 6057 } 6058 6059 // Quantize the preblend color used to key the cache, as only the high bits are 6060 // used to determine the amount of preblending. This avoids excessive cache use. 6061 // This roughly matches the quantization used in WebRender and Skia. 6062 static DeviceColor QuantizePreblendColor(const DeviceColor& aColor, 6063 bool aUseSubpixelAA) { 6064 int32_t r = int32_t(aColor.r * 255.0f + 0.5f); 6065 int32_t g = int32_t(aColor.g * 255.0f + 0.5f); 6066 int32_t b = int32_t(aColor.b * 255.0f + 0.5f); 6067 // Skia only uses the high 3 bits of each color component to cache preblend 6068 // ramp tables. 6069 constexpr int32_t lumBits = 3; 6070 constexpr int32_t floorMask = ((1 << lumBits) - 1) << (8 - lumBits); 6071 if (!aUseSubpixelAA) { 6072 // If not using subpixel AA, then quantize only the luminance, stored in the 6073 // G channel. 6074 g = (r * 54 + g * 183 + b * 19) >> 8; 6075 g &= floorMask; 6076 r = g; 6077 b = g; 6078 } else { 6079 r &= floorMask; 6080 g &= floorMask; 6081 b &= floorMask; 6082 } 6083 return DeviceColor{r / 255.0f, g / 255.0f, b / 255.0f, 1.0f}; 6084 } 6085 6086 // Draws glyphs to the WebGL target by trying to generate a cached texture for 6087 // the text run that can be subsequently reused to quickly render the text run 6088 // without using any software surfaces. 6089 bool SharedContextWebgl::DrawGlyphsAccel(ScaledFont* aFont, 6090 const GlyphBuffer& aBuffer, 6091 const Pattern& aPattern, 6092 const DrawOptions& aOptions, 6093 const StrokeOptions* aStrokeOptions, 6094 bool aUseSubpixelAA) { 6095 // Look for an existing glyph cache on the font. If not there, create it. 6096 GlyphCache* cache = 6097 static_cast<GlyphCache*>(aFont->GetUserData(&mGlyphCacheKey)); 6098 if (!cache) { 6099 cache = new GlyphCache(aFont); 6100 aFont->AddUserData(&mGlyphCacheKey, cache, ReleaseGlyphCache); 6101 mGlyphCaches.insertFront(cache); 6102 } 6103 6104 // Check if the buffer contains non-renderable whitespace characters before 6105 // any other expensive checks. 6106 if (cache->IsWhitespace(aBuffer)) { 6107 return true; 6108 } 6109 6110 // Whether the font may use bitmaps. If so, we need to render the glyphs with 6111 // color as grayscale bitmaps will use the color while color emoji will not, 6112 // with no easy way to know ahead of time. We currently have to check the 6113 // rasterized result to see if there are any color glyphs. To render subpixel 6114 // masks, we need to know that the rasterized result actually represents a 6115 // subpixel mask rather than try to interpret it as a normal RGBA result such 6116 // as for color emoji. 6117 bool useBitmaps = !aStrokeOptions && aFont->MayUseBitmaps() && 6118 aOptions.mCompositionOp != CompositionOp::OP_CLEAR; 6119 // Hash the incoming text run and looking for a matching entry. 6120 DeviceColor color = aOptions.mCompositionOp == CompositionOp::OP_CLEAR 6121 ? DeviceColor(1, 1, 1, 1) 6122 : static_cast<const ColorPattern&>(aPattern).mColor; 6123 #if defined(XP_MACOSX) 6124 // macOS uses gamma-aware blending with font smooth from subpixel AA. 6125 // If font smoothing is requested, even if there is no subpixel AA, gamma- 6126 // aware blending might be used and differing amounts of dilation might be 6127 // applied. 6128 bool usePreblend = aUseSubpixelAA || 6129 (aFont->GetType() == FontType::MAC && 6130 static_cast<ScaledFontMac*>(aFont)->UseFontSmoothing()); 6131 #elif defined(XP_WIN) 6132 // Windows uses gamma-aware blending via ClearType with grayscale and subpixel 6133 // AA. 6134 bool usePreblend = 6135 aUseSubpixelAA || aOptions.mAntialiasMode != AntialiasMode::NONE; 6136 #else 6137 // FreeType backends currently don't use any preblending. 6138 bool usePreblend = false; 6139 #endif 6140 6141 // If the font has bitmaps, use the color directly. Otherwise, the texture 6142 // holds a grayscale mask, so encode the key's subpixel state in the color. 6143 const Matrix& currentTransform = mCurrentTarget->GetTransform(); 6144 IntPoint quantizeScale = QuantizeScale(aFont, currentTransform); 6145 Matrix quantizeTransform = currentTransform; 6146 quantizeTransform.PostScale(quantizeScale.x, quantizeScale.y); 6147 HashNumber hash = 6148 GlyphCacheEntry::HashGlyphs(aBuffer, quantizeTransform, quantizeScale); 6149 DeviceColor colorOrMask = 6150 useBitmaps ? color 6151 : (usePreblend ? QuantizePreblendColor(color, aUseSubpixelAA) 6152 : DeviceColor::Mask(aUseSubpixelAA ? 1 : 0, 1)); 6153 IntRect clipRect(IntPoint(), mViewportSize); 6154 RefPtr<GlyphCacheEntry> entry = 6155 cache->FindEntry(aBuffer, colorOrMask, quantizeTransform, quantizeScale, 6156 clipRect, hash, aStrokeOptions); 6157 if (!entry) { 6158 // For small text runs, bounds computations can be expensive relative to the 6159 // cost of looking up a cache result. Avoid doing local bounds computations 6160 // until actually inserting the entry into the cache. 6161 Maybe<Rect> bounds = mCurrentTarget->mSkia->GetGlyphLocalBounds( 6162 aFont, aBuffer, aPattern, aStrokeOptions, aOptions); 6163 if (!bounds) { 6164 // Assume the buffer is full of whitespace characters that should be 6165 // remembered for subsequent lookups. 6166 cache->SetLastWhitespace(aBuffer); 6167 return true; 6168 } 6169 // Transform the local bounds into device space so that we know how big 6170 // the cached texture will be. 6171 Rect xformBounds = currentTransform.TransformBounds(*bounds); 6172 // Check if the transform flattens out the bounds before rounding. 6173 if (xformBounds.IsEmpty()) { 6174 return true; 6175 } 6176 IntRect fullBounds = RoundedOut(xformBounds); 6177 IntRect clipBounds = fullBounds.Intersect(clipRect); 6178 // Check if the bounds are completely clipped out. 6179 if (clipBounds.IsEmpty()) { 6180 return true; 6181 } 6182 entry = cache->InsertEntry(aBuffer, colorOrMask, quantizeTransform, 6183 quantizeScale, clipBounds, fullBounds, hash, 6184 aStrokeOptions); 6185 if (!entry) { 6186 return false; 6187 } 6188 } 6189 6190 // The bounds of the entry may have a different transform offset from the 6191 // bounds of the currently drawn text run. The entry bounds are relative to 6192 // the entry's quantized offset already, so just move the bounds to the new 6193 // offset. 6194 IntRect intBounds = entry->GetBounds(); 6195 IntPoint newOffset = 6196 QuantizeOffset(quantizeTransform, quantizeScale, aBuffer); 6197 intBounds += 6198 IntPoint(newOffset.x / quantizeScale.x, newOffset.y / quantizeScale.y); 6199 // Ensure there is a clear border around the text. This must be applied only 6200 // after clipping so that we always have some border texels for filtering. 6201 intBounds.Inflate(2); 6202 6203 RefPtr<TextureHandle> handle = entry->GetHandle(); 6204 if (handle && handle->IsValid()) { 6205 // If there is an entry with a valid cached texture handle, then try 6206 // to draw with that. If that for some reason failed, then fall back 6207 // to using the Skia target as that means we were preventing from 6208 // drawing to the WebGL context based on something other than the 6209 // texture. 6210 SurfacePattern pattern(nullptr, ExtendMode::CLAMP, 6211 Matrix::Translation(intBounds.TopLeft())); 6212 if (DrawRectAccel(Rect(intBounds), pattern, aOptions, 6213 useBitmaps ? Nothing() : Some(color), &handle, false, 6214 true, true)) { 6215 return true; 6216 } 6217 } else { 6218 handle = nullptr; 6219 6220 // If we get here, either there wasn't a cached texture handle or it 6221 // wasn't valid. Render the text run into a temporary target. 6222 RefPtr<DrawTargetSkia> textDT = new DrawTargetSkia; 6223 if (textDT->Init(intBounds.Size(), 6224 useBitmaps || usePreblend || aUseSubpixelAA 6225 ? SurfaceFormat::B8G8R8A8 6226 : SurfaceFormat::A8)) { 6227 textDT->SetTransform(currentTransform * 6228 Matrix::Translation(-intBounds.TopLeft())); 6229 textDT->SetPermitSubpixelAA(aUseSubpixelAA); 6230 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, 6231 aOptions.mAntialiasMode); 6232 // If bitmaps might be used, then we have to supply the color, as color 6233 // emoji may ignore it while grayscale bitmaps may use it, with no way to 6234 // know ahead of time. If we are using preblending in some form, then the 6235 // output also will depend on the supplied color. Otherwise, assume the 6236 // output will be a mask and just render it white to determine intensity. 6237 if (!useBitmaps && usePreblend) { 6238 textDT->DrawGlyphMask(aFont, aBuffer, color, aStrokeOptions, 6239 drawOptions); 6240 } else { 6241 ColorPattern colorPattern(useBitmaps ? color : DeviceColor(1, 1, 1, 1)); 6242 if (aStrokeOptions) { 6243 textDT->StrokeGlyphs(aFont, aBuffer, colorPattern, *aStrokeOptions, 6244 drawOptions); 6245 } else { 6246 textDT->FillGlyphs(aFont, aBuffer, colorPattern, drawOptions); 6247 } 6248 } 6249 RefPtr<SourceSurface> textSurface = textDT->Snapshot(); 6250 if (textSurface) { 6251 // If we don't expect the text surface to contain color glyphs 6252 // such as from subpixel AA, then do one final check to see if 6253 // any ended up in the result. If not, extract the alpha values 6254 // from the surface so we can render it as a mask. 6255 if (textSurface->GetFormat() != SurfaceFormat::A8 && 6256 !CheckForColorGlyphs(textSurface)) { 6257 textSurface = ExtractAlpha(textSurface, !useBitmaps); 6258 if (!textSurface) { 6259 // Failed extracting alpha for the text surface... 6260 return false; 6261 } 6262 } 6263 // Attempt to upload the rendered text surface into a texture 6264 // handle and draw it. 6265 SurfacePattern pattern(textSurface, ExtendMode::CLAMP, 6266 Matrix::Translation(intBounds.TopLeft())); 6267 if (DrawRectAccel(Rect(intBounds), pattern, aOptions, 6268 useBitmaps ? Nothing() : Some(color), &handle, false, 6269 true) && 6270 handle) { 6271 // If drawing succeeded, then the text surface was uploaded to 6272 // a texture handle. Assign it to the glyph cache entry. 6273 entry->Link(handle); 6274 } else { 6275 // If drawing failed, remove the entry from the cache. 6276 entry->Unlink(); 6277 } 6278 return true; 6279 } 6280 } 6281 } 6282 6283 // Avoid filling cache with empty entries. 6284 entry->Unlink(); 6285 return false; 6286 } 6287 6288 void DrawTargetWebgl::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, 6289 const Pattern& aPattern, 6290 const DrawOptions& aOptions) { 6291 if (!aFont || !aBuffer.mNumGlyphs) { 6292 return; 6293 } 6294 6295 bool useSubpixelAA = ShouldUseSubpixelAA(aFont, aOptions); 6296 6297 if (mWebglValid && SupportsDrawOptions(aOptions) && 6298 aPattern.GetType() == PatternType::COLOR && PrepareContext() && 6299 mSharedContext->DrawGlyphsAccel(aFont, aBuffer, aPattern, aOptions, 6300 nullptr, useSubpixelAA)) { 6301 return; 6302 } 6303 6304 // If not able to cache the text run to a texture, then just fall back to 6305 // drawing with the Skia target. 6306 if (useSubpixelAA) { 6307 // Subpixel AA does not support layering because the subpixel masks can't 6308 // blend with the over op. 6309 MarkSkiaChanged(); 6310 } else { 6311 MarkSkiaChanged(aOptions); 6312 } 6313 mSkia->FillGlyphs(aFont, aBuffer, aPattern, aOptions); 6314 } 6315 6316 // Attempts to read the contents of the WebGL context into the Skia target. 6317 bool DrawTargetWebgl::ReadIntoSkia() { 6318 if (mSkiaValid) { 6319 return false; 6320 } 6321 bool didReadback = false; 6322 if (mWebglValid) { 6323 uint8_t* data = nullptr; 6324 IntSize size; 6325 int32_t stride; 6326 SurfaceFormat format; 6327 if (mIsClear) { 6328 // If the WebGL target is still clear, then just clear the Skia target. 6329 mSkia->DetachAllSnapshots(); 6330 mSkiaNoClip->FillRect(Rect(mSkiaNoClip->GetRect()), GetClearPattern(), 6331 DrawOptions(1.0f, CompositionOp::OP_SOURCE)); 6332 } else { 6333 // If there's no existing snapshot and we can successfully map the Skia 6334 // target for reading, then try to read into that. 6335 if (!mSnapshot && mSkia->LockBits(&data, &size, &stride, &format)) { 6336 (void)ReadInto(data, stride); 6337 mSkia->ReleaseBits(data); 6338 } else if (RefPtr<SourceSurface> snapshot = Snapshot()) { 6339 // Otherwise, fall back to getting a snapshot from WebGL if available 6340 // and then copying that to Skia. 6341 mSkia->CopySurface(snapshot, GetRect(), IntPoint(0, 0)); 6342 } 6343 didReadback = true; 6344 } 6345 } 6346 mSkiaValid = true; 6347 // The Skia data is flat after reading, so disable any layering. 6348 mSkiaLayer = false; 6349 return didReadback; 6350 } 6351 6352 // Reads data from the WebGL context and blends it with the current Skia layer. 6353 void DrawTargetWebgl::FlattenSkia() { 6354 if (!mSkiaValid || !mSkiaLayer) { 6355 return; 6356 } 6357 mSkiaLayer = false; 6358 if (mSkiaLayerClear) { 6359 // If the WebGL target is clear, then there is nothing to blend. 6360 return; 6361 } 6362 if (RefPtr<DataSourceSurface> base = ReadSnapshot()) { 6363 mSkia->DetachAllSnapshots(); 6364 mSkiaNoClip->DrawSurface(base, Rect(GetRect()), Rect(GetRect()), 6365 DrawSurfaceOptions(SamplingFilter::POINT), 6366 DrawOptions(1.f, CompositionOp::OP_DEST_OVER)); 6367 } 6368 } 6369 6370 // Attempts to draw the contents of the Skia target into the WebGL context. 6371 bool DrawTargetWebgl::FlushFromSkia() { 6372 // If the WebGL context has been lost, then mark it as invalid and fail. 6373 if (mSharedContext->IsContextLost()) { 6374 mWebglValid = false; 6375 return false; 6376 } 6377 // The WebGL target is already valid, so there is nothing to do. 6378 if (mWebglValid) { 6379 return true; 6380 } 6381 // Ensure that DrawRect doesn't recursively call into FlushFromSkia. If 6382 // the Skia target isn't valid, then it doesn't matter what is in the the 6383 // WebGL target either, so only try to blend if there is a valid Skia target. 6384 mWebglValid = true; 6385 if (mSkiaValid) { 6386 AutoRestoreContext restore(this); 6387 6388 // If the Skia target is clear, then there is no need to use a snapshot. 6389 // Directly clear the WebGL target instead. 6390 if (mIsClear) { 6391 if (!DrawRect(Rect(GetRect()), GetClearPattern(), 6392 DrawOptions(1.0f, CompositionOp::OP_SOURCE), Nothing(), 6393 nullptr, false, false, true)) { 6394 mWebglValid = false; 6395 return false; 6396 } 6397 return true; 6398 } 6399 6400 RefPtr<SourceSurface> skiaSnapshot = mSkia->Snapshot(); 6401 if (!skiaSnapshot) { 6402 // There's a valid Skia target to draw to, but for some reason there is 6403 // no available snapshot, so just keep using the Skia target. 6404 mWebglValid = false; 6405 return false; 6406 } 6407 6408 // If there is no layer, then just upload it directly. 6409 if (!mSkiaLayer) { 6410 if (PrepareContext(false) && MarkChanged()) { 6411 if (RefPtr<DataSourceSurface> data = skiaSnapshot->GetDataSurface()) { 6412 mSharedContext->UploadSurface(data, mFormat, GetRect(), IntPoint(), 6413 false, false, mTex); 6414 return true; 6415 } 6416 } 6417 // Failed to upload the Skia snapshot. 6418 mWebglValid = false; 6419 return false; 6420 } 6421 6422 SurfacePattern pattern(skiaSnapshot, ExtendMode::CLAMP); 6423 // If there is a layer, blend the snapshot with the WebGL context. 6424 if (!DrawRect(Rect(GetRect()), pattern, 6425 DrawOptions(1.0f, CompositionOp::OP_OVER), Nothing(), 6426 &mSnapshotTexture, false, false, true, true)) { 6427 // If accelerated drawing failed for some reason, then leave the Skia 6428 // target unchanged. 6429 mWebglValid = false; 6430 return false; 6431 } 6432 } 6433 return true; 6434 } 6435 6436 void DrawTargetWebgl::UsageProfile::BeginFrame() { 6437 // Reset the usage profile counters for the new frame. 6438 mFallbacks = 0; 6439 mLayers = 0; 6440 mCacheMisses = 0; 6441 mCacheHits = 0; 6442 mUncachedDraws = 0; 6443 mReadbacks = 0; 6444 } 6445 6446 void DrawTargetWebgl::UsageProfile::EndFrame() { 6447 bool failed = false; 6448 // If we hit a complete fallback to software rendering, or if cache misses 6449 // were more than cutoff ratio of all requests, then we consider the frame as 6450 // having failed performance profiling. 6451 float cacheRatio = 6452 StaticPrefs::gfx_canvas_accelerated_profile_cache_miss_ratio(); 6453 if (mFallbacks > 0 || 6454 float(mCacheMisses + mReadbacks + mLayers) > 6455 cacheRatio * float(mCacheMisses + mCacheHits + mUncachedDraws + 6456 mReadbacks + mLayers)) { 6457 failed = true; 6458 } 6459 if (failed) { 6460 ++mFailedFrames; 6461 } 6462 ++mFrameCount; 6463 } 6464 6465 bool DrawTargetWebgl::UsageProfile::RequiresRefresh() const { 6466 // If we've rendered at least the required number of frames for a profile and 6467 // more than the cutoff ratio of frames did not meet performance criteria, 6468 // then we should stop using an accelerated canvas. 6469 uint32_t profileFrames = StaticPrefs::gfx_canvas_accelerated_profile_frames(); 6470 if (!profileFrames || mFrameCount < profileFrames) { 6471 return false; 6472 } 6473 float failRatio = 6474 StaticPrefs::gfx_canvas_accelerated_profile_fallback_ratio(); 6475 return mFailedFrames > failRatio * mFrameCount; 6476 } 6477 6478 void SharedContextWebgl::CachePrefs() { 6479 uint32_t capacity = StaticPrefs::gfx_canvas_accelerated_gpu_path_size() << 20; 6480 if (capacity != mPathVertexCapacity) { 6481 mPathVertexCapacity = capacity; 6482 if (mPathCache) { 6483 mPathCache->ClearVertexRanges(); 6484 } 6485 if (mPathVertexBuffer) { 6486 ResetPathVertexBuffer(); 6487 } 6488 } 6489 6490 mPathMaxComplexity = 6491 StaticPrefs::gfx_canvas_accelerated_gpu_path_complexity(); 6492 6493 mPathAAStroke = StaticPrefs::gfx_canvas_accelerated_aa_stroke_enabled(); 6494 mPathWGRStroke = StaticPrefs::gfx_canvas_accelerated_stroke_to_fill_path(); 6495 } 6496 6497 // For use within CanvasRenderingContext2D, called on BorrowDrawTarget. 6498 void DrawTargetWebgl::BeginFrame(bool aInvalidContents) { 6499 // If still rendering into the Skia target, switch back to the WebGL 6500 // context. 6501 if (!mWebglValid) { 6502 if (aInvalidContents) { 6503 // If nothing needs to persist, just mark the WebGL context valid. 6504 mWebglValid = true; 6505 // Even if the Skia framebuffer is marked clear, since the WebGL 6506 // context is not valid, its contents may be out-of-date and not 6507 // necessarily clear. 6508 mIsClear = false; 6509 } else { 6510 FlushFromSkia(); 6511 } 6512 } 6513 // Check if we need to clear out any cached because of memory pressure. 6514 mSharedContext->ClearCachesIfNecessary(); 6515 // Cache any prefs for the frame. 6516 mSharedContext->CachePrefs(); 6517 mProfile.BeginFrame(); 6518 } 6519 6520 // For use within CanvasRenderingContext2D, called on ReturnDrawTarget. 6521 void DrawTargetWebgl::EndFrame() { 6522 if (StaticPrefs::gfx_canvas_accelerated_debug()) { 6523 // Draw a green rectangle in the upper right corner to indicate 6524 // acceleration. 6525 IntRect corner = IntRect(mSize.width - 16, 0, 16, 16).Intersect(GetRect()); 6526 DrawRect(Rect(corner), ColorPattern(DeviceColor(0.0f, 1.0f, 0.0f, 1.0f)), 6527 DrawOptions(), Nothing(), nullptr, false, false); 6528 } 6529 mProfile.EndFrame(); 6530 // Ensure we're not somehow using more than the allowed texture memory. 6531 mSharedContext->PruneTextureMemory(); 6532 // Signal that we're done rendering the frame in case no present occurs. 6533 mSharedContext->mWebgl->EndOfFrame(); 6534 // Check if we need to clear out any cached because of memory pressure. 6535 mSharedContext->ClearCachesIfNecessary(); 6536 } 6537 6538 bool DrawTargetWebgl::CopyToSwapChain( 6539 layers::TextureType aTextureType, layers::RemoteTextureId aId, 6540 layers::RemoteTextureOwnerId aOwnerId, 6541 layers::RemoteTextureOwnerClient* aOwnerClient) { 6542 if (!mWebglValid && !FlushFromSkia()) { 6543 return false; 6544 } 6545 6546 // Copy and swizzle the WebGL framebuffer to the swap chain front buffer. 6547 webgl::SwapChainOptions options; 6548 options.bgra = true; 6549 // Allow async present to be toggled on for accelerated Canvas2D 6550 // independent of WebGL via pref. 6551 options.forceAsyncPresent = 6552 StaticPrefs::gfx_canvas_accelerated_async_present(); 6553 options.remoteTextureId = aId; 6554 options.remoteTextureOwnerId = aOwnerId; 6555 return mSharedContext->mWebgl->CopyToSwapChain(mFramebuffer, aTextureType, 6556 options, aOwnerClient); 6557 } 6558 6559 std::shared_ptr<gl::SharedSurface> SharedContextWebgl::ExportSharedSurface( 6560 layers::TextureType aTextureType, SourceSurface* aSurface) { 6561 RefPtr<WebGLTexture> tex = GetCompatibleSnapshot(aSurface, nullptr, false); 6562 if (!tex) { 6563 return nullptr; 6564 } 6565 if (!mExportFramebuffer) { 6566 mExportFramebuffer = mWebgl->CreateFramebuffer(); 6567 } 6568 mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mExportFramebuffer); 6569 webgl::FbAttachInfo attachInfo; 6570 attachInfo.tex = tex; 6571 mWebgl->FramebufferAttach(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, 6572 LOCAL_GL_TEXTURE_2D, attachInfo); 6573 // Copy and swizzle the WebGL framebuffer to the swap chain front buffer. 6574 webgl::SwapChainOptions options; 6575 options.bgra = true; 6576 std::shared_ptr<gl::SharedSurface> sharedSurface; 6577 if (mWebgl->CopyToSwapChain(mExportFramebuffer, aTextureType, options)) { 6578 if (gl::SwapChain* swapChain = 6579 mWebgl->GetSwapChain(mExportFramebuffer, false)) { 6580 sharedSurface = swapChain->FrontBuffer(); 6581 } 6582 } 6583 RestoreCurrentTarget(); 6584 return sharedSurface; 6585 } 6586 6587 already_AddRefed<SourceSurface> SharedContextWebgl::ImportSurfaceDescriptor( 6588 const layers::SurfaceDescriptor& aDesc, const IntSize& aSize, 6589 SurfaceFormat aFormat) { 6590 if (IsContextLost()) { 6591 return nullptr; 6592 } 6593 6594 RefPtr<TextureHandle> handle = 6595 AllocateTextureHandle(aFormat, aSize, true, true); 6596 if (!handle) { 6597 return nullptr; 6598 } 6599 BackingTexture* backing = handle->GetBackingTexture(); 6600 RefPtr<WebGLTexture> tex = backing->GetWebGLTexture(); 6601 if (mLastTexture != tex) { 6602 mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex); 6603 mLastTexture = tex; 6604 } 6605 if (!backing->IsInitialized()) { 6606 backing->MarkInitialized(); 6607 InitTexParameters(tex); 6608 if (handle->GetSize() != backing->GetSize()) { 6609 UploadSurface(nullptr, backing->GetFormat(), 6610 IntRect(IntPoint(), backing->GetSize()), IntPoint(), true, 6611 true); 6612 } 6613 } 6614 6615 IntRect bounds = handle->GetBounds(); 6616 webgl::TexUnpackBlobDesc texDesc = { 6617 LOCAL_GL_TEXTURE_2D, {uint32_t(aSize.width), uint32_t(aSize.height), 1}}; 6618 texDesc.sd = Some(aDesc); 6619 texDesc.structuredSrcSize = uvec2::FromSize(aSize); 6620 GLenum intFormat = 6621 aFormat == SurfaceFormat::A8 ? LOCAL_GL_R8 : LOCAL_GL_RGBA8; 6622 GLenum extFormat = 6623 aFormat == SurfaceFormat::A8 ? LOCAL_GL_RED : LOCAL_GL_RGBA; 6624 webgl::PackingInfo texPI = {extFormat, LOCAL_GL_UNSIGNED_BYTE}; 6625 mWebgl->TexImage(0, handle->GetSize() == backing->GetSize() ? intFormat : 0, 6626 {uint32_t(bounds.x), uint32_t(bounds.y), 0}, texPI, texDesc); 6627 6628 RefPtr<SourceSurfaceWebgl> surface = new SourceSurfaceWebgl(this); 6629 surface->SetHandle(handle); 6630 return surface.forget(); 6631 } 6632 6633 already_AddRefed<SourceSurface> DrawTargetWebgl::ImportSurfaceDescriptor( 6634 const layers::SurfaceDescriptor& aDesc, const IntSize& aSize, 6635 SurfaceFormat aFormat) { 6636 return mSharedContext->ImportSurfaceDescriptor(aDesc, aSize, aFormat); 6637 } 6638 6639 already_AddRefed<DrawTarget> DrawTargetWebgl::CreateSimilarDrawTarget( 6640 const IntSize& aSize, SurfaceFormat aFormat) const { 6641 return mSkia->CreateSimilarDrawTarget(aSize, aFormat); 6642 } 6643 6644 bool DrawTargetWebgl::CanCreateSimilarDrawTarget(const IntSize& aSize, 6645 SurfaceFormat aFormat) const { 6646 return mSkia->CanCreateSimilarDrawTarget(aSize, aFormat); 6647 } 6648 6649 RefPtr<DrawTarget> DrawTargetWebgl::CreateClippedDrawTarget( 6650 const Rect& aBounds, SurfaceFormat aFormat) { 6651 return mSkia->CreateClippedDrawTarget(aBounds, aFormat); 6652 } 6653 6654 already_AddRefed<SourceSurface> DrawTargetWebgl::CreateSourceSurfaceFromData( 6655 unsigned char* aData, const IntSize& aSize, int32_t aStride, 6656 SurfaceFormat aFormat) const { 6657 return mSkia->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat); 6658 } 6659 6660 already_AddRefed<SourceSurface> 6661 DrawTargetWebgl::CreateSourceSurfaceFromNativeSurface( 6662 const NativeSurface& aSurface) const { 6663 return mSkia->CreateSourceSurfaceFromNativeSurface(aSurface); 6664 } 6665 6666 already_AddRefed<SourceSurface> DrawTargetWebgl::OptimizeSourceSurface( 6667 SourceSurface* aSurface) const { 6668 if (aSurface->GetUnderlyingType() == SurfaceType::WEBGL) { 6669 return do_AddRef(aSurface); 6670 } 6671 return mSkia->OptimizeSourceSurface(aSurface); 6672 } 6673 6674 already_AddRefed<SourceSurface> 6675 DrawTargetWebgl::OptimizeSourceSurfaceForUnknownAlpha( 6676 SourceSurface* aSurface) const { 6677 return mSkia->OptimizeSourceSurfaceForUnknownAlpha(aSurface); 6678 } 6679 6680 already_AddRefed<GradientStops> DrawTargetWebgl::CreateGradientStops( 6681 GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const { 6682 return mSkia->CreateGradientStops(aStops, aNumStops, aExtendMode); 6683 } 6684 6685 already_AddRefed<FilterNode> DrawTargetWebgl::CreateFilter(FilterType aType) { 6686 return FilterNodeWebgl::Create(aType); 6687 } 6688 6689 already_AddRefed<FilterNode> DrawTargetWebgl::DeferFilterInput( 6690 const Path* aPath, const Pattern& aPattern, const IntRect& aSourceRect, 6691 const IntPoint& aDestOffset, const DrawOptions& aOptions, 6692 const StrokeOptions* aStrokeOptions) { 6693 RefPtr<FilterNode> filter = new FilterNodeDeferInputWebgl( 6694 do_AddRef((Path*)aPath), aPattern, aSourceRect, 6695 GetTransform().PostTranslate(aDestOffset), aOptions, aStrokeOptions); 6696 return filter.forget(); 6697 } 6698 6699 void DrawTargetWebgl::DrawFilter(FilterNode* aNode, const Rect& aSourceRect, 6700 const Point& aDestPoint, 6701 const DrawOptions& aOptions) { 6702 if (!aNode || aNode->GetBackendType() != FILTER_BACKEND_WEBGL) { 6703 return; 6704 } 6705 auto* webglFilter = static_cast<FilterNodeWebgl*>(aNode); 6706 webglFilter->Draw(this, aSourceRect, aDestPoint, aOptions); 6707 } 6708 6709 void DrawTargetWebgl::DrawFilterFallback(FilterNode* aNode, 6710 const Rect& aSourceRect, 6711 const Point& aDestPoint, 6712 const DrawOptions& aOptions) { 6713 MarkSkiaChanged(aOptions); 6714 mSkia->DrawFilter(aNode, aSourceRect, aDestPoint, aOptions); 6715 } 6716 6717 bool DrawTargetWebgl::Draw3DTransformedSurface(SourceSurface* aSurface, 6718 const Matrix4x4& aMatrix) { 6719 MarkSkiaChanged(); 6720 return mSkia->Draw3DTransformedSurface(aSurface, aMatrix); 6721 } 6722 6723 void DrawTargetWebgl::PushLayer(bool aOpaque, Float aOpacity, 6724 SourceSurface* aMask, 6725 const Matrix& aMaskTransform, 6726 const IntRect& aBounds, bool aCopyBackground) { 6727 PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds, 6728 aCopyBackground, CompositionOp::OP_OVER); 6729 } 6730 6731 void DrawTargetWebgl::PushLayerWithBlend(bool aOpaque, Float aOpacity, 6732 SourceSurface* aMask, 6733 const Matrix& aMaskTransform, 6734 const IntRect& aBounds, 6735 bool aCopyBackground, 6736 CompositionOp aCompositionOp) { 6737 MarkSkiaChanged(DrawOptions(aOpacity, aCompositionOp)); 6738 mSkia->PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds, 6739 aCopyBackground, aCompositionOp); 6740 ++mLayerDepth; 6741 SetPermitSubpixelAA(mSkia->GetPermitSubpixelAA()); 6742 } 6743 6744 void DrawTargetWebgl::PopLayer() { 6745 MOZ_ASSERT(mSkiaValid); 6746 MOZ_ASSERT(mLayerDepth > 0); 6747 --mLayerDepth; 6748 mSkia->PopLayer(); 6749 SetPermitSubpixelAA(mSkia->GetPermitSubpixelAA()); 6750 } 6751 6752 } // namespace mozilla::gfx