CanvasChild.cpp (26919B)
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 "CanvasChild.h" 8 9 #include "MainThreadUtils.h" 10 #include "mozilla/dom/WorkerPrivate.h" 11 #include "mozilla/dom/WorkerRef.h" 12 #include "mozilla/dom/WorkerRunnable.h" 13 #include "mozilla/gfx/CanvasManagerChild.h" 14 #include "mozilla/gfx/CanvasShutdownManager.h" 15 #include "mozilla/gfx/DrawTargetRecording.h" 16 #include "mozilla/gfx/gfxVars.h" 17 #include "mozilla/gfx/Tools.h" 18 #include "mozilla/gfx/Rect.h" 19 #include "mozilla/gfx/Point.h" 20 #include "mozilla/ipc/Endpoint.h" 21 #include "mozilla/ipc/ProcessChild.h" 22 #include "mozilla/ipc/SharedMemoryHandle.h" 23 #include "mozilla/layers/CanvasDrawEventRecorder.h" 24 #include "mozilla/layers/ImageDataSerializer.h" 25 #include "mozilla/layers/SourceSurfaceSharedData.h" 26 #include "mozilla/AppShutdown.h" 27 #include "mozilla/Mutex.h" 28 #include "mozilla/StaticPrefs_gfx.h" 29 #include "nsIObserverService.h" 30 #include "nsICanvasRenderingContextInternal.h" 31 #include "RecordedCanvasEventImpl.h" 32 33 namespace mozilla { 34 namespace layers { 35 36 class RecorderHelpers final : public CanvasDrawEventRecorder::Helpers { 37 public: 38 NS_DECL_OWNINGTHREAD 39 40 explicit RecorderHelpers(const RefPtr<CanvasChild>& aCanvasChild) 41 : mCanvasChild(aCanvasChild) {} 42 43 ~RecorderHelpers() override = default; 44 45 bool InitTranslator( 46 TextureType aTextureType, TextureType aWebglTextureType, 47 gfx::BackendType aBackendType, 48 ipc::MutableSharedMemoryHandle&& aReadHandle, 49 nsTArray<ipc::ReadOnlySharedMemoryHandle>&& aBufferHandles, 50 CrossProcessSemaphoreHandle&& aReaderSem, 51 CrossProcessSemaphoreHandle&& aWriterSem) override { 52 NS_ASSERT_OWNINGTHREAD(RecorderHelpers); 53 if (NS_WARN_IF(!mCanvasChild)) { 54 return false; 55 } 56 return mCanvasChild->SendInitTranslator( 57 aTextureType, aWebglTextureType, aBackendType, std::move(aReadHandle), 58 std::move(aBufferHandles), std::move(aReaderSem), 59 std::move(aWriterSem)); 60 } 61 62 bool AddBuffer(ipc::ReadOnlySharedMemoryHandle&& aBufferHandle) override { 63 NS_ASSERT_OWNINGTHREAD(RecorderHelpers); 64 if (!mCanvasChild) { 65 return false; 66 } 67 return mCanvasChild->SendAddBuffer(std::move(aBufferHandle)); 68 } 69 70 bool ReaderClosed() override { 71 NS_ASSERT_OWNINGTHREAD(RecorderHelpers); 72 if (!mCanvasChild) { 73 return false; 74 } 75 return !mCanvasChild->CanSend() || AppShutdown::IsShutdownImpending(); 76 } 77 78 bool RestartReader() override { 79 NS_ASSERT_OWNINGTHREAD(RecorderHelpers); 80 if (!mCanvasChild) { 81 return false; 82 } 83 return mCanvasChild->SendRestartTranslation(); 84 } 85 86 already_AddRefed<CanvasChild> GetCanvasChild() const override { 87 RefPtr<CanvasChild> canvasChild(mCanvasChild); 88 return canvasChild.forget(); 89 } 90 91 private: 92 const WeakPtr<CanvasChild> mCanvasChild; 93 }; 94 95 // Limit the number of in-flight export surfaces 96 static Atomic<uint32_t> sCurrentExportSurfaces(0); 97 // Limit the memory used by in-flight export surfaces 98 static Atomic<size_t> sCurrentExportSurfaceMemory(0); 99 100 class SourceSurfaceCanvasRecording final : public gfx::SourceSurface { 101 public: 102 MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceCanvasRecording, final) 103 104 SourceSurfaceCanvasRecording( 105 const RemoteTextureOwnerId aTextureOwnerId, 106 const RefPtr<gfx::SourceSurface>& aRecordedSuface, 107 CanvasChild* aCanvasChild, 108 const RefPtr<CanvasDrawEventRecorder>& aRecorder) 109 : mTextureOwnerId(aTextureOwnerId), 110 mRecordedSurface(aRecordedSuface), 111 mCanvasChild(aCanvasChild), 112 mRecorder(aRecorder) { 113 // It's important that AddStoredObject is called first because that will 114 // run any pending processing required by recorded objects that have been 115 // deleted off the main thread. 116 mRecorder->AddStoredObject(this); 117 mRecorder->RecordEvent(RecordedAddSurfaceAlias(this, aRecordedSuface)); 118 } 119 120 ~SourceSurfaceCanvasRecording() { 121 ReferencePtr surfaceAlias = this; 122 ReferencePtr exportID = mExportID; 123 if (NS_IsMainThread()) { 124 ReleaseOnMainThread(std::move(mRecorder), surfaceAlias, 125 std::move(mRecordedSurface), std::move(mCanvasChild), 126 exportID); 127 return; 128 } 129 130 mRecorder->AddPendingDeletion( 131 [recorder = std::move(mRecorder), surfaceAlias, 132 aliasedSurface = std::move(mRecordedSurface), 133 canvasChild = std::move(mCanvasChild), exportID]() mutable -> void { 134 ReleaseOnMainThread(std::move(recorder), surfaceAlias, 135 std::move(aliasedSurface), std::move(canvasChild), 136 exportID); 137 }); 138 } 139 140 gfx::SurfaceType GetType() const final { return mRecordedSurface->GetType(); } 141 142 gfx::IntSize GetSize() const final { return mRecordedSurface->GetSize(); } 143 144 gfx::SurfaceFormat GetFormat() const final { 145 return mRecordedSurface->GetFormat(); 146 } 147 148 already_AddRefed<gfx::DataSourceSurface> GetDataSurface() final { 149 EnsureDataSurfaceOnMainThread(); 150 return do_AddRef(mDataSourceSurface); 151 } 152 153 void AttachSurface() { mDetached = false; } 154 void DetachSurface() { mDetached = true; } 155 156 void InvalidateDataSurface() { 157 if (mDataSourceSurface && mMayInvalidate) { 158 // This must be the only reference to the data left. 159 MOZ_ASSERT(mDataSourceSurface->hasOneRef()); 160 mDataSourceSurface = 161 gfx::Factory::CopyDataSourceSurface(mDataSourceSurface); 162 mMayInvalidate = false; 163 } 164 } 165 166 already_AddRefed<gfx::SourceSurface> ExtractSubrect( 167 const gfx::IntRect& aRect) final { 168 return mRecordedSurface->ExtractSubrect(aRect); 169 } 170 171 static size_t GetExportSurfaceSize(gfx::SourceSurface* aSurface) { 172 return ImageDataSerializer::ComputeRGBBufferSize(aSurface->GetSize(), 173 aSurface->GetFormat()); 174 } 175 176 bool GetSurfaceDescriptor(SurfaceDescriptor& aDesc) final { 177 static Atomic<uintptr_t> sNextExportID(0); 178 if (!mExportID) { 179 if (++sCurrentExportSurfaces > 180 StaticPrefs::gfx_canvas_accelerated_max_export_surfaces()) { 181 --sCurrentExportSurfaces; 182 return false; 183 } 184 size_t bytes = GetExportSurfaceSize(mRecordedSurface); 185 if ((sCurrentExportSurfaceMemory += bytes) > 186 StaticPrefs::gfx_canvas_accelerated_max_export_surface_memory()) { 187 --sCurrentExportSurfaces; 188 sCurrentExportSurfaceMemory -= bytes; 189 return false; 190 } 191 mExportID = gfx::ReferencePtr(++sNextExportID); 192 mRecorder->RecordEvent(RecordedAddExportSurface(mExportID, this)); 193 } 194 aDesc = SurfaceDescriptorCanvasSurface( 195 static_cast<gfx::CanvasManagerChild*>(mCanvasChild->Manager())->Id(), 196 mCanvasChild->Id(), uintptr_t(mExportID)); 197 return true; 198 } 199 200 private: 201 void EnsureDataSurfaceOnMainThread() { 202 // The data can only be retrieved on the main thread. 203 if (!mDataSourceSurface && NS_IsMainThread()) { 204 mDataSourceSurface = mCanvasChild->GetDataSurface( 205 mTextureOwnerId, mRecordedSurface, mDetached, mMayInvalidate); 206 } 207 } 208 209 // Used to ensure that clean-up that requires it is done on the main thread. 210 static void ReleaseOnMainThread(RefPtr<CanvasDrawEventRecorder> aRecorder, 211 ReferencePtr aSurfaceAlias, 212 RefPtr<gfx::SourceSurface> aAliasedSurface, 213 RefPtr<CanvasChild> aCanvasChild, 214 ReferencePtr aExportID) { 215 MOZ_ASSERT(NS_IsMainThread()); 216 217 aRecorder->RemoveStoredObject(aSurfaceAlias); 218 aRecorder->RecordEvent(RecordedRemoveSurfaceAlias(aSurfaceAlias)); 219 if (aExportID) { 220 aRecorder->RecordEvent(RecordedRemoveExportSurface(aExportID)); 221 --sCurrentExportSurfaces; 222 size_t bytes = GetExportSurfaceSize(aAliasedSurface); 223 sCurrentExportSurfaceMemory -= bytes; 224 } 225 aAliasedSurface = nullptr; 226 aCanvasChild = nullptr; 227 aRecorder = nullptr; 228 } 229 230 const RemoteTextureOwnerId mTextureOwnerId; 231 RefPtr<gfx::SourceSurface> mRecordedSurface; 232 RefPtr<CanvasChild> mCanvasChild; 233 RefPtr<CanvasDrawEventRecorder> mRecorder; 234 RefPtr<gfx::DataSourceSurface> mDataSourceSurface; 235 bool mDetached = false; 236 bool mMayInvalidate = false; 237 ReferencePtr mExportID; 238 }; 239 240 class CanvasDataShmemHolder { 241 public: 242 CanvasDataShmemHolder( 243 const std::shared_ptr<ipc::ReadOnlySharedMemoryMapping>& aShmem, 244 CanvasChild* aCanvasChild) 245 : mMutex("CanvasChild::DataShmemHolder::mMutex"), 246 mShmem(aShmem), 247 mCanvasChild(aCanvasChild) {} 248 249 bool Init(dom::ThreadSafeWorkerRef* aWorkerRef) { 250 if (!aWorkerRef) { 251 return true; 252 } 253 254 RefPtr<dom::StrongWorkerRef> workerRef = dom::StrongWorkerRef::Create( 255 aWorkerRef->Private(), "CanvasChild::DataShmemHolder", 256 [this]() { DestroyWorker(); }); 257 if (NS_WARN_IF(!workerRef)) { 258 return false; 259 } 260 261 MutexAutoLock lock(mMutex); 262 mWorkerRef = new dom::ThreadSafeWorkerRef(workerRef); 263 return true; 264 } 265 266 void Destroy() { 267 class DestroyRunnable final : public dom::WorkerThreadRunnable { 268 public: 269 explicit DestroyRunnable(CanvasDataShmemHolder* aShmemHolder) 270 : dom::WorkerThreadRunnable("CanvasDataShmemHolder::Destroy"), 271 mShmemHolder(aShmemHolder) {} 272 273 bool WorkerRun(JSContext* aCx, 274 dom::WorkerPrivate* aWorkerPrivate) override { 275 mShmemHolder->Destroy(); 276 return true; 277 } 278 279 void PostRun(JSContext* aCx, dom::WorkerPrivate* aWorkerPrivate, 280 bool aRunResult) override {} 281 282 bool PreDispatch(dom::WorkerPrivate* aWorkerPrivate) override { 283 return true; 284 } 285 286 void PostDispatch(dom::WorkerPrivate* aWorkerPrivate, 287 bool aDispatchResult) override {} 288 289 private: 290 CanvasDataShmemHolder* mShmemHolder; 291 }; 292 293 mMutex.Lock(); 294 295 if (mCanvasChild) { 296 if (mWorkerRef) { 297 if (!mWorkerRef->Private()->IsOnCurrentThread()) { 298 auto task = MakeRefPtr<DestroyRunnable>(this); 299 dom::WorkerPrivate* worker = mWorkerRef->Private(); 300 mMutex.Unlock(); 301 task->Dispatch(worker); 302 return; 303 } 304 } else if (!NS_IsMainThread()) { 305 mMutex.Unlock(); 306 NS_DispatchToMainThread(NS_NewRunnableFunction( 307 "CanvasDataShmemHolder::Destroy", [this]() { Destroy(); })); 308 return; 309 } 310 311 mCanvasChild->ReturnDataSurfaceShmem(std::move(mShmem)); 312 mCanvasChild = nullptr; 313 mWorkerRef = nullptr; 314 } 315 316 mMutex.Unlock(); 317 delete this; 318 } 319 320 void DestroyWorker() { 321 MutexAutoLock lock(mMutex); 322 mCanvasChild = nullptr; 323 mWorkerRef = nullptr; 324 } 325 326 private: 327 Mutex mMutex; 328 std::shared_ptr<ipc::ReadOnlySharedMemoryMapping> mShmem; 329 RefPtr<CanvasChild> mCanvasChild MOZ_GUARDED_BY(mMutex); 330 RefPtr<dom::ThreadSafeWorkerRef> mWorkerRef MOZ_GUARDED_BY(mMutex); 331 }; 332 333 CanvasChild::CanvasChild(dom::ThreadSafeWorkerRef* aWorkerRef) 334 : mWorkerRef(aWorkerRef) {} 335 336 CanvasChild::~CanvasChild() { MOZ_ASSERT(!mWorkerRef); } 337 338 static void NotifyCanvasDeviceChanged() { 339 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 340 if (obs) { 341 obs->NotifyObservers(nullptr, "canvas-device-reset", nullptr); 342 } 343 } 344 345 ipc::IPCResult CanvasChild::RecvNotifyDeviceReset( 346 const nsTArray<RemoteTextureOwnerId>& aOwnerIds) { 347 NS_ASSERT_OWNINGTHREAD(CanvasChild); 348 349 if (auto* manager = gfx::CanvasShutdownManager::MaybeGet()) { 350 manager->OnRemoteCanvasReset(aOwnerIds); 351 } 352 353 mRecorder->RecordEvent(RecordedDeviceResetAcknowledged()); 354 return IPC_OK(); 355 } 356 357 /* static */ bool CanvasChild::mDeactivated = false; 358 359 ipc::IPCResult CanvasChild::RecvDeactivate() { 360 NS_ASSERT_OWNINGTHREAD(CanvasChild); 361 362 RefPtr<CanvasChild> self(this); 363 mDeactivated = true; 364 if (auto* cm = gfx::CanvasManagerChild::Get()) { 365 cm->DeactivateCanvas(); 366 } 367 NotifyCanvasDeviceChanged(); 368 return IPC_OK(); 369 } 370 371 ipc::IPCResult CanvasChild::RecvBlockCanvas() { 372 mBlocked = true; 373 if (auto* cm = gfx::CanvasManagerChild::Get()) { 374 cm->BlockCanvas(); 375 } 376 return IPC_OK(); 377 } 378 379 bool CanvasChild::EnsureRecorder(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, 380 TextureType aTextureType, 381 TextureType aWebglTextureType) { 382 NS_ASSERT_OWNINGTHREAD(CanvasChild); 383 384 if (!mRecorder) { 385 gfx::BackendType backendType = 386 gfxPlatform::GetPlatform()->GetPreferredCanvasBackend(); 387 auto recorder = MakeRefPtr<CanvasDrawEventRecorder>(mWorkerRef); 388 if (!recorder->Init(aTextureType, aWebglTextureType, backendType, 389 MakeUnique<RecorderHelpers>(this))) { 390 return false; 391 } 392 393 mRecorder = recorder.forget(); 394 } 395 396 if (NS_WARN_IF(mRecorder->GetTextureType() != aTextureType)) { 397 // The recorder has already been initialized with a different type. This can 398 // happen if there is a device reset or fallback that causes a switch to a 399 // different unaccelerated texture type (i.e. unknown). In that case, just 400 // fall back to non-remote rendering. 401 return false; 402 } 403 404 EnsureDataSurfaceShmem(aSize, aFormat); 405 406 return true; 407 } 408 409 void CanvasChild::ActorDestroy(ActorDestroyReason aWhy) { 410 NS_ASSERT_OWNINGTHREAD(CanvasChild); 411 412 if (mRecorder) { 413 mRecorder->DetachResources(); 414 } 415 mTextureInfo.clear(); 416 } 417 418 void CanvasChild::Destroy() { 419 NS_ASSERT_OWNINGTHREAD(CanvasChild); 420 421 if (CanSend()) { 422 Send__delete__(this); 423 } 424 425 mWorkerRef = nullptr; 426 } 427 428 bool CanvasChild::EnsureBeginTransaction() { 429 NS_ASSERT_OWNINGTHREAD(CanvasChild); 430 431 if (!mIsInTransaction) { 432 RecordEvent(RecordedCanvasBeginTransaction()); 433 mIsInTransaction = true; 434 } 435 436 return true; 437 } 438 439 void CanvasChild::EndTransaction() { 440 NS_ASSERT_OWNINGTHREAD(CanvasChild); 441 442 if (mIsInTransaction) { 443 RecordEvent(RecordedCanvasEndTransaction()); 444 mIsInTransaction = false; 445 mDormant = false; 446 } else if (mRecorder) { 447 // Schedule to drop free buffers if we have no non-empty transactions. 448 if (!mDormant) { 449 mDormant = true; 450 NS_DelayedDispatchToCurrentThread( 451 NewRunnableMethod("CanvasChild::DropFreeBuffersWhenDormant", this, 452 &CanvasChild::DropFreeBuffersWhenDormant), 453 StaticPrefs::gfx_canvas_remote_drop_buffer_milliseconds()); 454 } 455 } 456 457 // If we are continuously drawing/recording, then we need to periodically 458 // flush our external surface/image references, to ensure they actually get 459 // freed on a timely basis. 460 if (mRecorder) { 461 mRecorder->ClearProcessedExternalSurfaces(); 462 mRecorder->ClearProcessedExternalImages(); 463 } 464 465 ++mTransactionsSinceGetDataSurface; 466 } 467 468 void CanvasChild::DropFreeBuffersWhenDormant() { 469 NS_ASSERT_OWNINGTHREAD(CanvasChild); 470 // Drop any free buffers if we have not had any non-empty transactions. 471 if (mDormant && mRecorder) { 472 mRecorder->DropFreeBuffers(); 473 // Notify CanvasTranslator it is dormant. 474 SendDropFreeBuffersWhenDormant(); 475 } 476 } 477 478 void CanvasChild::ClearCachedResources() { 479 NS_ASSERT_OWNINGTHREAD(CanvasChild); 480 if (mRecorder) { 481 mRecorder->DropFreeBuffers(); 482 // Notify CanvasTranslator it is about to be minimized. 483 SendClearCachedResources(); 484 } 485 } 486 487 bool CanvasChild::ShouldBeCleanedUp() const { 488 NS_ASSERT_OWNINGTHREAD(CanvasChild); 489 490 // Always return true if we've been deactivated. 491 if (Deactivated()) { 492 return true; 493 } 494 495 // We can only be cleaned up if nothing else references our recorder. 496 return !mRecorder || (mRecorder->hasOneRef() && mTextureInfo.empty()); 497 } 498 499 already_AddRefed<gfx::DrawTargetRecording> CanvasChild::CreateDrawTarget( 500 const RemoteTextureOwnerId& aTextureOwnerId, gfx::IntSize aSize, 501 gfx::SurfaceFormat aFormat) { 502 NS_ASSERT_OWNINGTHREAD(CanvasChild); 503 MOZ_ASSERT(mTextureInfo.find(aTextureOwnerId) == mTextureInfo.end()); 504 505 if (!mRecorder) { 506 return nullptr; 507 } 508 509 RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget( 510 gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat); 511 RefPtr<gfx::DrawTargetRecording> dt = MakeAndAddRef<gfx::DrawTargetRecording>( 512 mRecorder, aTextureOwnerId, dummyDt, aSize); 513 dt->SetOptimizeTransform(true); 514 515 mTextureInfo.insert({aTextureOwnerId, {}}); 516 517 return dt.forget(); 518 } 519 520 size_t CanvasChild::SizeOfDataSurfaceShmem(gfx::IntSize aSize, 521 gfx::SurfaceFormat aFormat) { 522 if (!mRecorder) { 523 return 0; 524 } 525 size_t sizeRequired = 526 ImageDataSerializer::ComputeRGBBufferSize(aSize, aFormat); 527 return sizeRequired > 0 ? ipc::shared_memory::PageAlignedSize(sizeRequired) 528 : 0; 529 } 530 531 bool CanvasChild::ShouldGrowDataSurfaceShmem(size_t aSizeRequired) { 532 return aSizeRequired > 0 && (!mDataSurfaceShmemAvailable || 533 mDataSurfaceShmem->Size() < aSizeRequired); 534 } 535 536 bool CanvasChild::EnsureDataSurfaceShmem(size_t aSizeRequired) { 537 NS_ASSERT_OWNINGTHREAD(CanvasChild); 538 539 if (!aSizeRequired) { 540 return false; 541 } 542 543 if (ShouldGrowDataSurfaceShmem(aSizeRequired)) { 544 RecordEvent(RecordedPauseTranslation()); 545 auto shmemHandle = ipc::shared_memory::Create(aSizeRequired); 546 if (!shmemHandle) { 547 return false; 548 } 549 550 auto roMapping = shmemHandle.AsReadOnly().Map(); 551 if (!roMapping) { 552 return false; 553 } 554 555 auto id = ++mNextDataSurfaceShmemId; 556 if (!id) { 557 // If ids overflow, ensure that zero is reserved. 558 id = ++mNextDataSurfaceShmemId; 559 } 560 if (!SendSetDataSurfaceBuffer(id, std::move(shmemHandle))) { 561 return false; 562 } 563 564 mDataSurfaceShmem = std::make_shared<ipc::ReadOnlySharedMemoryMapping>( 565 std::move(roMapping)); 566 mDataSurfaceShmemAvailable = true; 567 } 568 569 MOZ_ASSERT(mDataSurfaceShmemAvailable); 570 return true; 571 } 572 573 bool CanvasChild::EnsureDataSurfaceShmem(gfx::IntSize aSize, 574 gfx::SurfaceFormat aFormat) { 575 size_t sizeRequired = SizeOfDataSurfaceShmem(aSize, aFormat); 576 if (!sizeRequired) { 577 return false; 578 } 579 580 return EnsureDataSurfaceShmem(sizeRequired); 581 } 582 583 void CanvasChild::RecordEvent(const gfx::RecordedEvent& aEvent) { 584 NS_ASSERT_OWNINGTHREAD(CanvasChild); 585 586 // We drop mRecorder in ActorDestroy to break the reference cycle. 587 if (!mRecorder) { 588 return; 589 } 590 591 mRecorder->RecordEvent(aEvent); 592 } 593 594 int64_t CanvasChild::CreateCheckpoint() { 595 NS_ASSERT_OWNINGTHREAD(CanvasChild); 596 return mRecorder->CreateCheckpoint(); 597 } 598 599 already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface( 600 const RemoteTextureOwnerId aTextureOwnerId, 601 const gfx::SourceSurface* aSurface, bool aDetached, bool& aMayInvalidate) { 602 NS_ASSERT_OWNINGTHREAD(CanvasChild); 603 MOZ_ASSERT(aSurface); 604 605 // mTransactionsSinceGetDataSurface is used to determine if we want to prepare 606 // a DataSourceSurface in the GPU process up front at the end of the 607 // transaction, but that only makes sense if the canvas JS is requesting data 608 // in between transactions. 609 if (!mIsInTransaction) { 610 mTransactionsSinceGetDataSurface = 0; 611 } 612 613 if (!EnsureBeginTransaction()) { 614 return nullptr; 615 } 616 617 gfx::IntSize ssSize = aSurface->GetSize(); 618 gfx::SurfaceFormat ssFormat = aSurface->GetFormat(); 619 auto stride = ImageDataSerializer::ComputeRGBStride(ssFormat, ssSize.width); 620 621 // Shmem is only valid if the surface is the latest snapshot (not detached). 622 if (!aDetached) { 623 // If there is a shmem associated with this snapshot id, then we want to try 624 // use that directly without having to allocate a new shmem for retrieval. 625 auto it = mTextureInfo.find(aTextureOwnerId); 626 if (it != mTextureInfo.end() && it->second.mSnapshotShmem) { 627 const auto* shmemPtr = it->second.mSnapshotShmem->DataAs<uint8_t>(); 628 MOZ_ASSERT(shmemPtr); 629 mRecorder->RecordEvent(RecordedPrepareShmem(aTextureOwnerId)); 630 auto checkpoint = CreateCheckpoint(); 631 if (NS_WARN_IF(!mRecorder->WaitForCheckpoint(checkpoint))) { 632 return nullptr; 633 } 634 auto* closure = 635 new CanvasDataShmemHolder(it->second.mSnapshotShmem, this); 636 if (NS_WARN_IF(!closure->Init(mWorkerRef))) { 637 delete closure; 638 return nullptr; 639 } 640 // We can cast away the const of `shmemPtr` to match the call because the 641 // DataSourceSurface will not be written to. 642 RefPtr<gfx::DataSourceSurface> dataSurface = 643 gfx::Factory::CreateWrappingDataSourceSurface( 644 const_cast<uint8_t*>(shmemPtr), stride, ssSize, ssFormat, 645 ReleaseDataShmemHolder, closure); 646 aMayInvalidate = true; 647 return dataSurface.forget(); 648 } 649 } 650 651 size_t sizeRequired = SizeOfDataSurfaceShmem(ssSize, ssFormat); 652 if (!sizeRequired) { 653 return nullptr; 654 } 655 656 RecordEvent(RecordedCacheDataSurface(aSurface)); 657 658 if (!EnsureDataSurfaceShmem(sizeRequired)) { 659 return nullptr; 660 } 661 662 RecordEvent(RecordedGetDataForSurface(mNextDataSurfaceShmemId, aSurface)); 663 auto checkpoint = CreateCheckpoint(); 664 if (NS_WARN_IF(!mRecorder->WaitForCheckpoint(checkpoint))) { 665 return nullptr; 666 } 667 668 auto* closure = new CanvasDataShmemHolder(mDataSurfaceShmem, this); 669 if (NS_WARN_IF(!closure->Init(mWorkerRef))) { 670 delete closure; 671 return nullptr; 672 } 673 674 mDataSurfaceShmemAvailable = false; 675 676 const auto* data = mDataSurfaceShmem->DataAs<uint8_t>(); 677 678 // We can cast away the const of `data` to match the call because the 679 // DataSourceSurface will not be written to. 680 RefPtr<gfx::DataSourceSurface> dataSurface = 681 gfx::Factory::CreateWrappingDataSourceSurface( 682 const_cast<uint8_t*>(data), stride, ssSize, ssFormat, 683 ReleaseDataShmemHolder, closure); 684 aMayInvalidate = false; 685 return dataSurface.forget(); 686 } 687 688 /* static */ void CanvasChild::ReleaseDataShmemHolder(void* aClosure) { 689 auto* shmemHolder = static_cast<CanvasDataShmemHolder*>(aClosure); 690 shmemHolder->Destroy(); 691 } 692 693 already_AddRefed<gfx::SourceSurface> CanvasChild::WrapSurface( 694 const RefPtr<gfx::SourceSurface>& aSurface, 695 const RemoteTextureOwnerId aTextureOwnerId) { 696 NS_ASSERT_OWNINGTHREAD(CanvasChild); 697 698 if (!aSurface) { 699 return nullptr; 700 } 701 702 return MakeAndAddRef<SourceSurfaceCanvasRecording>(aTextureOwnerId, aSurface, 703 this, mRecorder); 704 } 705 706 void CanvasChild::ReturnDataSurfaceShmem( 707 std::shared_ptr<ipc::ReadOnlySharedMemoryMapping>&& aDataSurfaceShmem) { 708 // We can only reuse the latest data surface shmem. 709 if (aDataSurfaceShmem == mDataSurfaceShmem) { 710 MOZ_ASSERT(!mDataSurfaceShmemAvailable); 711 mDataSurfaceShmemAvailable = true; 712 } 713 } 714 715 void CanvasChild::AttachSurface(const RefPtr<gfx::SourceSurface>& aSurface) { 716 if (auto* surface = 717 static_cast<SourceSurfaceCanvasRecording*>(aSurface.get())) { 718 surface->AttachSurface(); 719 } 720 } 721 722 void CanvasChild::DetachSurface(const RefPtr<gfx::SourceSurface>& aSurface, 723 bool aInvalidate) { 724 if (auto* surface = 725 static_cast<SourceSurfaceCanvasRecording*>(aSurface.get())) { 726 surface->DetachSurface(); 727 if (aInvalidate) { 728 surface->InvalidateDataSurface(); 729 } 730 } 731 } 732 733 ipc::IPCResult CanvasChild::RecvNotifyRequiresRefresh( 734 const RemoteTextureOwnerId aTextureOwnerId) { 735 auto it = mTextureInfo.find(aTextureOwnerId); 736 if (it != mTextureInfo.end()) { 737 it->second.mRequiresRefresh = true; 738 } 739 return IPC_OK(); 740 } 741 742 bool CanvasChild::RequiresRefresh( 743 const RemoteTextureOwnerId aTextureOwnerId) const { 744 if (mBlocked) { 745 return true; 746 } 747 auto it = mTextureInfo.find(aTextureOwnerId); 748 if (it != mTextureInfo.end()) { 749 return it->second.mRequiresRefresh; 750 } 751 return false; 752 } 753 754 ipc::IPCResult CanvasChild::RecvSnapshotShmem( 755 const RemoteTextureOwnerId aTextureOwnerId, 756 ipc::ReadOnlySharedMemoryHandle&& aShmemHandle, 757 SnapshotShmemResolver&& aResolve) { 758 auto it = mTextureInfo.find(aTextureOwnerId); 759 if (it != mTextureInfo.end()) { 760 auto shmem = aShmemHandle.Map(); 761 if (NS_WARN_IF(!shmem)) { 762 shmem = nullptr; 763 } else { 764 it->second.mSnapshotShmem = 765 std::make_shared<ipc::ReadOnlySharedMemoryMapping>(std::move(shmem)); 766 } 767 aResolve(true); 768 } else { 769 aResolve(false); 770 } 771 return IPC_OK(); 772 } 773 774 ipc::IPCResult CanvasChild::RecvNotifyTextureDestruction( 775 const RemoteTextureOwnerId aTextureOwnerId) { 776 auto it = mTextureInfo.find(aTextureOwnerId); 777 if (it == mTextureInfo.end()) { 778 MOZ_ASSERT(!CanSend()); 779 return IPC_OK(); 780 } 781 782 mTextureInfo.erase(aTextureOwnerId); 783 return IPC_OK(); 784 } 785 786 already_AddRefed<gfx::SourceSurface> CanvasChild::SnapshotExternalCanvas( 787 gfx::DrawTargetRecording* aTarget, 788 nsICanvasRenderingContextInternal* aCanvas, 789 mozilla::ipc::IProtocol* aActor) { 790 // SnapshotExternalCanvas is only valid to use if using Accelerated Canvas2D 791 // with the pending events queue enabled. This ensures WebGL and AC2D are 792 // running under the same thread, and that events can be paused or resumed 793 // while synchronizing between WebGL and AC2D. 794 if (!gfx::gfxVars::UseAcceleratedCanvas2D() || 795 !StaticPrefs::gfx_canvas_remote_use_canvas_translator_event_AtStartup()) { 796 return nullptr; 797 } 798 799 gfx::SurfaceFormat format = aCanvas->GetIsOpaque() 800 ? gfx::SurfaceFormat::B8G8R8X8 801 : gfx::SurfaceFormat::B8G8R8A8; 802 gfx::IntSize size(aCanvas->GetWidth(), aCanvas->GetHeight()); 803 // Create a source sourface that will be associated with the snapshot. 804 RefPtr<gfx::SourceSurface> surface = 805 aTarget->CreateExternalSourceSurface(size, format); 806 if (!surface) { 807 return nullptr; 808 } 809 810 // Pause translation until the sync-id identifying the snapshot is received. 811 uint64_t syncId = ++mLastSyncId; 812 mRecorder->RecordEvent(RecordedAwaitTranslationSync(syncId)); 813 814 // Flush WebGL to cause any IPDL messages to get sent at this sync point. 815 aCanvas->SyncSnapshot(); 816 817 // Once the IPDL message is sent to generate the snapshot, resolve the sync-id 818 // to a surface in the recording stream. The AwaitTranslationSync above will 819 // ensure this event is not translated until the snapshot is generated first. 820 mRecorder->RecordEvent(aTarget, 821 RecordedResolveExternalSnapshot( 822 syncId, gfx::ReferencePtr(surface), size, format)); 823 824 uint32_t managerId = static_cast<gfx::CanvasManagerChild*>(Manager())->Id(); 825 ActorId canvasId = aActor->Id(); 826 827 // Actually send the request via IPDL to snapshot the external WebGL canvas. 828 SendSnapshotExternalCanvas(syncId, managerId, canvasId); 829 830 return surface.forget(); 831 } 832 833 } // namespace layers 834 } // namespace mozilla