1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "OffscreenCanvasDisplayHelper.h"
8 #include "mozilla/dom/Document.h"
9 #include "mozilla/dom/WorkerRef.h"
10 #include "mozilla/dom/WorkerRunnable.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/CanvasManagerChild.h"
13 #include "mozilla/gfx/DataSurfaceHelpers.h"
14 #include "mozilla/gfx/Swizzle.h"
15 #include "mozilla/layers/ImageBridgeChild.h"
16 #include "mozilla/layers/PersistentBufferProvider.h"
17 #include "mozilla/layers/TextureClientSharedSurface.h"
18 #include "mozilla/layers/TextureWrapperImage.h"
19 #include "mozilla/StaticPrefs_gfx.h"
20 #include "mozilla/SVGObserverUtils.h"
21 #include "nsICanvasRenderingContextInternal.h"
22 #include "nsRFPService.h"
24 namespace mozilla::dom
{
26 OffscreenCanvasDisplayHelper::OffscreenCanvasDisplayHelper(
27 HTMLCanvasElement
* aCanvasElement
, uint32_t aWidth
, uint32_t aHeight
)
28 : mMutex("mozilla::dom::OffscreenCanvasDisplayHelper"),
29 mCanvasElement(aCanvasElement
),
30 mImageProducerID(layers::ImageContainer::AllocateProducerID()) {
31 mData
.mSize
.width
= aWidth
;
32 mData
.mSize
.height
= aHeight
;
35 OffscreenCanvasDisplayHelper::~OffscreenCanvasDisplayHelper() = default;
37 void OffscreenCanvasDisplayHelper::DestroyElement() {
38 MOZ_ASSERT(NS_IsMainThread());
40 MutexAutoLock
lock(mMutex
);
41 if (mImageContainer
) {
42 mImageContainer
->ClearAllImages();
43 mImageContainer
= nullptr;
45 mFrontBufferSurface
= nullptr;
46 mCanvasElement
= nullptr;
49 void OffscreenCanvasDisplayHelper::DestroyCanvas() {
50 if (auto* cm
= gfx::CanvasManagerChild::Get()) {
51 cm
->EndCanvasTransaction();
54 MutexAutoLock
lock(mMutex
);
55 if (mImageContainer
) {
56 mImageContainer
->ClearAllImages();
57 mImageContainer
= nullptr;
59 mFrontBufferSurface
= nullptr;
60 mOffscreenCanvas
= nullptr;
64 CanvasContextType
OffscreenCanvasDisplayHelper::GetContextType() const {
65 MutexAutoLock
lock(mMutex
);
69 RefPtr
<layers::ImageContainer
> OffscreenCanvasDisplayHelper::GetImageContainer()
71 MutexAutoLock
lock(mMutex
);
72 return mImageContainer
;
75 void OffscreenCanvasDisplayHelper::UpdateContext(
76 OffscreenCanvas
* aOffscreenCanvas
, RefPtr
<ThreadSafeWorkerRef
>&& aWorkerRef
,
77 CanvasContextType aType
, const Maybe
<int32_t>& aChildId
) {
78 RefPtr
<layers::ImageContainer
> imageContainer
=
79 MakeRefPtr
<layers::ImageContainer
>(layers::ImageContainer::ASYNCHRONOUS
);
81 MutexAutoLock
lock(mMutex
);
83 mOffscreenCanvas
= aOffscreenCanvas
;
84 mWorkerRef
= std::move(aWorkerRef
);
86 mContextChildId
= aChildId
;
87 mImageContainer
= std::move(imageContainer
);
90 mContextManagerId
= Some(gfx::CanvasManagerChild::Get()->Id());
92 mContextManagerId
.reset();
95 MaybeQueueInvalidateElement();
98 void OffscreenCanvasDisplayHelper::FlushForDisplay() {
99 MOZ_ASSERT(NS_IsMainThread());
101 MutexAutoLock
lock(mMutex
);
103 // Without an OffscreenCanvas object bound to us, we either have not drawn
104 // using the canvas at all, or we have already destroyed it.
105 if (!mOffscreenCanvas
) {
109 // We assign/destroy the worker ref at the same time as the OffscreenCanvas
110 // ref, so we can only have the canvas without a worker ref if it exists on
113 // We queue to ensure that we have the same asynchronous update behaviour
114 // for a main thread and a worker based OffscreenCanvas.
115 mOffscreenCanvas
->QueueCommitToCompositor();
119 class FlushWorkerRunnable final
: public WorkerRunnable
{
121 FlushWorkerRunnable(WorkerPrivate
* aWorkerPrivate
,
122 OffscreenCanvasDisplayHelper
* aDisplayHelper
)
123 : WorkerRunnable(aWorkerPrivate
, "FlushWorkerRunnable"),
124 mDisplayHelper(aDisplayHelper
) {}
126 bool WorkerRun(JSContext
*, WorkerPrivate
*) override
{
127 // The OffscreenCanvas can only be freed on the worker thread, so we
128 // cannot be racing with an OffscreenCanvas::DestroyCanvas call and its
129 // destructor. We just need to make sure we don't call into
130 // OffscreenCanvas while holding the lock since it calls back into the
131 // OffscreenCanvasDisplayHelper.
132 RefPtr
<OffscreenCanvas
> canvas
;
134 MutexAutoLock
lock(mDisplayHelper
->mMutex
);
135 canvas
= mDisplayHelper
->mOffscreenCanvas
;
139 canvas
->CommitFrameToCompositor();
145 RefPtr
<OffscreenCanvasDisplayHelper
> mDisplayHelper
;
148 // Otherwise we are calling from the main thread during painting to a canvas
149 // on a worker thread.
150 auto task
= MakeRefPtr
<FlushWorkerRunnable
>(mWorkerRef
->Private(), this);
154 bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
155 nsICanvasRenderingContextInternal
* aContext
,
156 layers::TextureType aTextureType
,
157 const Maybe
<OffscreenCanvasDisplayData
>& aData
) {
158 auto endTransaction
= MakeScopeExit([&]() {
159 if (auto* cm
= gfx::CanvasManagerChild::Get()) {
160 cm
->EndCanvasTransaction();
164 MutexAutoLock
lock(mMutex
);
166 gfx::SurfaceFormat format
= gfx::SurfaceFormat::B8G8R8A8
;
167 layers::TextureFlags flags
= layers::TextureFlags::IMMUTABLE
;
169 if (!mCanvasElement
) {
170 // Our weak reference to the canvas element has been cleared, so we cannot
171 // present directly anymore.
177 MaybeQueueInvalidateElement();
180 if (mData
.mOwnerId
.isSome()) {
181 // No need to update the ImageContainer as the presentation itself is
182 // handled in the compositor process.
186 if (!mImageContainer
) {
190 if (mData
.mIsOpaque
) {
191 flags
|= layers::TextureFlags::IS_OPAQUE
;
192 format
= gfx::SurfaceFormat::B8G8R8X8
;
193 } else if (!mData
.mIsAlphaPremult
) {
194 flags
|= layers::TextureFlags::NON_PREMULTIPLIED
;
197 switch (mData
.mOriginPos
) {
198 case gl::OriginPos::BottomLeft
:
199 flags
|= layers::TextureFlags::ORIGIN_BOTTOM_LEFT
;
201 case gl::OriginPos::TopLeft
:
204 MOZ_ASSERT_UNREACHABLE("Unhandled origin position!");
208 auto imageBridge
= layers::ImageBridgeChild::GetSingleton();
213 bool paintCallbacks
= mData
.mDoPaintCallbacks
;
214 bool hasRemoteTextureDesc
= false;
215 RefPtr
<layers::Image
> image
;
216 RefPtr
<layers::TextureClient
> texture
;
217 RefPtr
<gfx::SourceSurface
> surface
;
218 Maybe
<layers::SurfaceDescriptor
> desc
;
219 RefPtr
<layers::FwdTransactionTracker
> tracker
;
222 MutexAutoUnlock
unlock(mMutex
);
223 if (paintCallbacks
) {
224 aContext
->OnBeforePaintTransaction();
227 desc
= aContext
->PresentFrontBuffer(nullptr, aTextureType
);
229 hasRemoteTextureDesc
=
231 layers::SurfaceDescriptor::TSurfaceDescriptorRemoteTexture
;
232 if (hasRemoteTextureDesc
) {
233 tracker
= aContext
->UseCompositableForwarder(imageBridge
);
235 flags
|= layers::TextureFlags::WAIT_FOR_REMOTE_TEXTURE_OWNER
;
239 if (layers::PersistentBufferProvider
* provider
=
240 aContext
->GetBufferProvider()) {
241 texture
= provider
->GetTextureClient();
246 aContext
->GetFrontBufferSnapshot(/* requireAlphaPremult */ false);
247 if (surface
&& surface
->GetType() == gfx::SurfaceType::WEBGL
) {
248 // Ensure we can map in the surface. If we get a SourceSurfaceWebgl
249 // surface, then it may not be backed by raw pixels yet. We need to
250 // map it on the owning thread rather than the ImageBridge thread.
251 gfx::DataSourceSurface::ScopedMap
map(
252 static_cast<gfx::DataSourceSurface
*>(surface
.get()),
253 gfx::DataSourceSurface::READ
);
254 if (!map
.IsMapped()) {
261 if (paintCallbacks
) {
262 aContext
->OnDidPaintTransaction();
266 // We save any current surface because we might need it in GetSnapshot. If we
267 // are on a worker thread and not WebGL, then this will be the only way we can
268 // access the pixel data on the main thread.
269 mFrontBufferSurface
= surface
;
271 // We do not use the ImageContainer plumbing with remote textures, so if we
272 // have that, we can just early return here.
273 if (hasRemoteTextureDesc
) {
274 const auto& textureDesc
= desc
->get_SurfaceDescriptorRemoteTexture();
275 imageBridge
->UpdateCompositable(mImageContainer
, textureDesc
.textureId(),
276 textureDesc
.ownerId(), mData
.mSize
, flags
,
282 auto surfaceImage
= MakeRefPtr
<layers::SourceSurfaceImage
>(surface
);
283 surfaceImage
->SetTextureFlags(flags
);
284 image
= surfaceImage
;
286 if (desc
&& !texture
) {
287 texture
= layers::SharedSurfaceTextureData::CreateTextureClient(
288 *desc
, format
, mData
.mSize
, flags
, imageBridge
);
291 image
= new layers::TextureWrapperImage(
292 texture
, gfx::IntRect(gfx::IntPoint(0, 0), texture
->GetSize()));
297 AutoTArray
<layers::ImageContainer::NonOwningImage
, 1> imageList
;
298 imageList
.AppendElement(layers::ImageContainer::NonOwningImage(
299 image
, TimeStamp(), mLastFrameID
++, mImageProducerID
));
300 mImageContainer
->SetCurrentImages(imageList
);
302 mImageContainer
->ClearAllImages();
308 void OffscreenCanvasDisplayHelper::MaybeQueueInvalidateElement() {
309 if (!mPendingInvalidate
) {
310 mPendingInvalidate
= true;
311 NS_DispatchToMainThread(NS_NewRunnableFunction(
312 "OffscreenCanvasDisplayHelper::InvalidateElement",
313 [self
= RefPtr
{this}] { self
->InvalidateElement(); }));
317 void OffscreenCanvasDisplayHelper::InvalidateElement() {
318 MOZ_ASSERT(NS_IsMainThread());
320 HTMLCanvasElement
* canvasElement
;
324 MutexAutoLock
lock(mMutex
);
325 MOZ_ASSERT(mPendingInvalidate
);
326 mPendingInvalidate
= false;
327 canvasElement
= mCanvasElement
;
332 SVGObserverUtils::InvalidateDirectRenderingObservers(canvasElement
);
333 canvasElement
->InvalidateCanvasPlaceholder(size
.width
, size
.height
);
334 canvasElement
->InvalidateCanvasContent(nullptr);
338 already_AddRefed
<gfx::SourceSurface
>
339 OffscreenCanvasDisplayHelper::TransformSurface(gfx::SourceSurface
* aSurface
,
341 bool aIsAlphaPremult
,
342 gl::OriginPos aOriginPos
) const {
347 if (aOriginPos
== gl::OriginPos::TopLeft
&& (!aHasAlpha
|| aIsAlphaPremult
)) {
348 // If we don't need to y-flip, and it is either opaque or premultiplied,
349 // we can just return the same surface.
350 return do_AddRef(aSurface
);
353 // Otherwise we need to copy and apply the necessary transformations.
354 RefPtr
<gfx::DataSourceSurface
> srcSurface
= aSurface
->GetDataSurface();
359 const auto size
= srcSurface
->GetSize();
360 const auto format
= srcSurface
->GetFormat();
362 RefPtr
<gfx::DataSourceSurface
> dstSurface
=
363 gfx::Factory::CreateDataSourceSurface(size
, format
, /* aZero */ false);
368 gfx::DataSourceSurface::ScopedMap
srcMap(srcSurface
,
369 gfx::DataSourceSurface::READ
);
370 gfx::DataSourceSurface::ScopedMap
dstMap(dstSurface
,
371 gfx::DataSourceSurface::WRITE
);
372 if (!srcMap
.IsMapped() || !dstMap
.IsMapped()) {
377 switch (aOriginPos
) {
378 case gl::OriginPos::BottomLeft
:
379 if (aHasAlpha
&& !aIsAlphaPremult
) {
380 success
= gfx::PremultiplyYFlipData(
381 srcMap
.GetData(), srcMap
.GetStride(), format
, dstMap
.GetData(),
382 dstMap
.GetStride(), format
, size
);
384 success
= gfx::SwizzleYFlipData(srcMap
.GetData(), srcMap
.GetStride(),
385 format
, dstMap
.GetData(),
386 dstMap
.GetStride(), format
, size
);
389 case gl::OriginPos::TopLeft
:
390 if (aHasAlpha
&& !aIsAlphaPremult
) {
391 success
= gfx::PremultiplyData(srcMap
.GetData(), srcMap
.GetStride(),
392 format
, dstMap
.GetData(),
393 dstMap
.GetStride(), format
, size
);
395 success
= gfx::SwizzleData(srcMap
.GetData(), srcMap
.GetStride(), format
,
396 dstMap
.GetData(), dstMap
.GetStride(), format
,
401 MOZ_ASSERT_UNREACHABLE("Unhandled origin position!");
410 return dstSurface
.forget();
413 already_AddRefed
<gfx::SourceSurface
>
414 OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
415 MOZ_ASSERT(NS_IsMainThread());
417 class SnapshotWorkerRunnable final
: public MainThreadWorkerRunnable
{
419 SnapshotWorkerRunnable(WorkerPrivate
* aWorkerPrivate
,
420 OffscreenCanvasDisplayHelper
* aDisplayHelper
)
421 : MainThreadWorkerRunnable(aWorkerPrivate
, "SnapshotWorkerRunnable"),
422 mMonitor("SnapshotWorkerRunnable::mMonitor"),
423 mDisplayHelper(aDisplayHelper
) {}
425 bool WorkerRun(JSContext
*, WorkerPrivate
*) override
{
426 // The OffscreenCanvas can only be freed on the worker thread, so we
427 // cannot be racing with an OffscreenCanvas::DestroyCanvas call and its
428 // destructor. We just need to make sure we don't call into
429 // OffscreenCanvas while holding the lock since it calls back into the
430 // OffscreenCanvasDisplayHelper.
431 RefPtr
<OffscreenCanvas
> canvas
;
433 MutexAutoLock
lock(mDisplayHelper
->mMutex
);
434 canvas
= mDisplayHelper
->mOffscreenCanvas
;
437 // Now that we are on the correct thread, we can extract the snapshot. If
438 // it is a Skia surface, perform a copy to threading issues.
439 RefPtr
<gfx::SourceSurface
> surface
;
441 if (auto* context
= canvas
->GetContext()) {
443 context
->GetFrontBufferSnapshot(/* requireAlphaPremult */ false);
444 if (surface
&& surface
->GetType() == gfx::SurfaceType::SKIA
) {
445 surface
= gfx::Factory::CopyDataSourceSurface(
446 static_cast<gfx::DataSourceSurface
*>(surface
.get()));
451 MonitorAutoLock
lock(mMonitor
);
452 mSurface
= std::move(surface
);
458 already_AddRefed
<gfx::SourceSurface
> Wait(int32_t aTimeoutMs
) {
459 MonitorAutoLock
lock(mMonitor
);
461 TimeDuration timeout
= TimeDuration::FromMilliseconds(aTimeoutMs
);
463 if (lock
.Wait(timeout
) == CVStatus::Timeout
) {
468 return mSurface
.forget();
473 RefPtr
<OffscreenCanvasDisplayHelper
> mDisplayHelper
;
474 RefPtr
<gfx::SourceSurface
> mSurface
MOZ_GUARDED_BY(mMonitor
);
475 bool mComplete
MOZ_GUARDED_BY(mMonitor
) = false;
480 gl::OriginPos originPos
;
481 HTMLCanvasElement
* canvasElement
;
482 RefPtr
<gfx::SourceSurface
> surface
;
483 RefPtr
<SnapshotWorkerRunnable
> workerRunnable
;
486 MutexAutoLock
lock(mMutex
);
487 #ifdef MOZ_WIDGET_ANDROID
488 // On Android, we cannot both display a GL context and read back the pixels.
489 if (mCanvasElement
) {
494 hasAlpha
= !mData
.mIsOpaque
;
495 isAlphaPremult
= mData
.mIsAlphaPremult
;
496 originPos
= mData
.mOriginPos
;
497 canvasElement
= mCanvasElement
;
500 MakeRefPtr
<SnapshotWorkerRunnable
>(mWorkerRef
->Private(), this);
501 workerRunnable
->Dispatch();
505 if (workerRunnable
) {
506 // We transferred to a DOM worker, so we need to do the readback on the
507 // owning thread and wait for the result.
508 surface
= workerRunnable
->Wait(
509 StaticPrefs::gfx_offscreencanvas_snapshot_timeout_ms());
510 } else if (canvasElement
) {
511 // If we have a context, it is owned by the main thread.
512 const auto* offscreenCanvas
= canvasElement
->GetOffscreenCanvas();
513 if (nsICanvasRenderingContextInternal
* context
=
514 offscreenCanvas
->GetContext()) {
516 context
->GetFrontBufferSnapshot(/* requireAlphaPremult */ false);
520 return TransformSurface(surface
, hasAlpha
, isAlphaPremult
, originPos
);
523 already_AddRefed
<layers::Image
> OffscreenCanvasDisplayHelper::GetAsImage() {
524 MOZ_ASSERT(NS_IsMainThread());
526 RefPtr
<gfx::SourceSurface
> surface
= GetSurfaceSnapshot();
530 return MakeAndAddRef
<layers::SourceSurfaceImage
>(surface
);
533 UniquePtr
<uint8_t[]> OffscreenCanvasDisplayHelper::GetImageBuffer(
534 int32_t* aOutFormat
, gfx::IntSize
* aOutImageSize
) {
535 RefPtr
<gfx::SourceSurface
> surface
= GetSurfaceSnapshot();
540 RefPtr
<gfx::DataSourceSurface
> dataSurface
= surface
->GetDataSurface();
545 *aOutFormat
= imgIEncoder::INPUT_FORMAT_HOSTARGB
;
546 *aOutImageSize
= dataSurface
->GetSize();
548 UniquePtr
<uint8_t[]> imageBuffer
= gfx::SurfaceToPackedBGRA(dataSurface
);
553 bool resistFingerprinting
;
554 nsICookieJarSettings
* cookieJarSettings
= nullptr;
556 MutexAutoLock
lock(mMutex
);
557 if (mCanvasElement
) {
558 Document
* doc
= mCanvasElement
->OwnerDoc();
559 resistFingerprinting
=
560 doc
->ShouldResistFingerprinting(RFPTarget::CanvasRandomization
);
561 if (resistFingerprinting
) {
562 cookieJarSettings
= doc
->CookieJarSettings();
565 resistFingerprinting
= nsContentUtils::ShouldResistFingerprinting(
566 "Fallback", RFPTarget::CanvasRandomization
);
570 if (resistFingerprinting
) {
571 nsRFPService::RandomizePixels(
572 cookieJarSettings
, imageBuffer
.get(), dataSurface
->GetSize().width
,
573 dataSurface
->GetSize().height
,
574 dataSurface
->GetSize().width
* dataSurface
->GetSize().height
* 4,
575 gfx::SurfaceFormat::A8R8G8B8_UINT32
);
580 } // namespace mozilla::dom