1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/dom/WebGPUBinding.h"
7 #include "CanvasContext.h"
9 #include "LayerUserData.h"
10 #include "nsDisplayList.h"
11 #include "mozilla/dom/HTMLCanvasElement.h"
12 #include "mozilla/gfx/CanvasManagerChild.h"
13 #include "mozilla/layers/CanvasRenderer.h"
14 #include "mozilla/layers/ImageDataSerializer.h"
15 #include "mozilla/layers/LayersSurfaces.h"
16 #include "mozilla/layers/RenderRootStateManager.h"
17 #include "mozilla/layers/WebRenderCanvasRenderer.h"
18 #include "mozilla/StaticPrefs_privacy.h"
19 #include "mozilla/SVGObserverUtils.h"
20 #include "ipc/WebGPUChild.h"
24 inline void ImplCycleCollectionTraverse(
25 nsCycleCollectionTraversalCallback
& aCallback
,
26 dom::GPUCanvasConfiguration
& aField
, const char* aName
, uint32_t aFlags
) {
27 aField
.TraverseForCC(aCallback
, aFlags
);
30 inline void ImplCycleCollectionUnlink(dom::GPUCanvasConfiguration
& aField
) {
37 inline void ImplCycleCollectionTraverse(
38 nsCycleCollectionTraversalCallback
& aCallback
,
39 const std::unique_ptr
<T
>& aField
, const char* aName
, uint32_t aFlags
) {
41 ImplCycleCollectionTraverse(aCallback
, *aField
, aName
, aFlags
);
46 inline void ImplCycleCollectionUnlink(std::unique_ptr
<T
>& aField
) {
50 } // namespace mozilla
54 namespace mozilla::webgpu
{
56 NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasContext
)
57 NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasContext
)
59 GPU_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(CanvasContext
, mConfig
,
64 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasContext
)
65 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
66 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal
)
67 NS_INTERFACE_MAP_ENTRY(nsISupports
)
72 CanvasContext::CanvasContext() = default;
74 CanvasContext::~CanvasContext() {
76 RemovePostRefreshObserver();
79 void CanvasContext::Cleanup() { Unconfigure(); }
81 JSObject
* CanvasContext::WrapObject(JSContext
* aCx
,
82 JS::Handle
<JSObject
*> aGivenProto
) {
83 return dom::GPUCanvasContext_Binding::Wrap(aCx
, this, aGivenProto
);
88 void CanvasContext::GetCanvas(
89 dom::OwningHTMLCanvasElementOrOffscreenCanvas
& aRetVal
) const {
91 aRetVal
.SetAsHTMLCanvasElement() = mCanvasElement
;
92 } else if (mOffscreenCanvas
) {
93 aRetVal
.SetAsOffscreenCanvas() = mOffscreenCanvas
;
96 "This should only happen briefly during CC Unlink, and no JS should "
101 void CanvasContext::Configure(const dom::GPUCanvasConfiguration
& aConfig
) {
104 // Bug 1864904: Failures in validation should throw a TypeError, per spec.
106 // these formats are guaranteed by the spec
107 switch (aConfig
.mFormat
) {
108 case dom::GPUTextureFormat::Rgba8unorm
:
109 case dom::GPUTextureFormat::Rgba8unorm_srgb
:
110 mGfxFormat
= gfx::SurfaceFormat::R8G8B8A8
;
112 case dom::GPUTextureFormat::Bgra8unorm
:
113 case dom::GPUTextureFormat::Bgra8unorm_srgb
:
114 mGfxFormat
= gfx::SurfaceFormat::B8G8R8A8
;
117 NS_WARNING("Specified swap chain format is not supported");
121 mConfig
.reset(new dom::GPUCanvasConfiguration(aConfig
));
122 mRemoteTextureOwnerId
= Some(layers::RemoteTextureOwnerId::GetNext());
123 mUseExternalTextureInSwapChain
=
124 wgpu_client_use_external_texture_in_swapChain(
125 aConfig
.mDevice
->mId
,
126 WebGPUChild::ConvertTextureFormat(aConfig
.mFormat
));
127 mTexture
= aConfig
.mDevice
->InitSwapChain(
128 mConfig
.get(), mRemoteTextureOwnerId
.ref(),
129 mUseExternalTextureInSwapChain
, mGfxFormat
, mCanvasSize
);
135 mTexture
->mTargetContext
= this;
136 mBridge
= aConfig
.mDevice
->GetBridge();
137 if (mCanvasElement
) {
138 mWaitingCanvasRendererInitialized
= true;
144 void CanvasContext::Unconfigure() {
145 if (mBridge
&& mBridge
->IsOpen() && mRemoteTextureOwnerId
.isSome()) {
146 mBridge
->SendSwapChainDrop(*mRemoteTextureOwnerId
);
148 mRemoteTextureOwnerId
= Nothing();
152 mGfxFormat
= gfx::SurfaceFormat::UNKNOWN
;
155 NS_IMETHODIMP
CanvasContext::SetDimensions(int32_t aWidth
, int32_t aHeight
) {
156 aWidth
= std::max(1, aWidth
);
157 aHeight
= std::max(1, aHeight
);
158 const auto newSize
= gfx::IntSize
{aWidth
, aHeight
};
159 if (newSize
== mCanvasSize
) return NS_OK
; // No-op no-change resizes.
161 mCanvasSize
= newSize
;
163 const auto copy
= dom::GPUCanvasConfiguration
{
164 *mConfig
}; // So we can't null it out on ourselves.
170 RefPtr
<Texture
> CanvasContext::GetCurrentTexture(ErrorResult
& aRv
) {
172 aRv
.ThrowOperationError("Canvas not configured");
177 MOZ_ASSERT(mRemoteTextureOwnerId
.isSome());
179 if (mNewTextureRequested
) {
180 mNewTextureRequested
= false;
182 mTexture
= mConfig
->mDevice
->CreateTextureForSwapChain(
183 mConfig
.get(), mCanvasSize
, mRemoteTextureOwnerId
.ref());
184 mTexture
->mTargetContext
= this;
189 void CanvasContext::MaybeQueueSwapChainPresent() {
190 if (mPendingSwapChainPresent
) {
194 mPendingSwapChainPresent
= true;
196 if (mWaitingCanvasRendererInitialized
) {
200 InvalidateCanvasContent();
203 Maybe
<layers::SurfaceDescriptor
> CanvasContext::SwapChainPresent() {
204 mPendingSwapChainPresent
= false;
205 if (!mBridge
|| !mBridge
->IsOpen() || mRemoteTextureOwnerId
.isNothing() ||
209 mLastRemoteTextureId
= Some(layers::RemoteTextureId::GetNext());
210 mBridge
->SwapChainPresent(mTexture
->mId
, *mLastRemoteTextureId
,
211 *mRemoteTextureOwnerId
);
212 if (mUseExternalTextureInSwapChain
) {
214 mNewTextureRequested
= true;
216 return Some(layers::SurfaceDescriptorRemoteTexture(*mLastRemoteTextureId
,
217 *mRemoteTextureOwnerId
));
220 bool CanvasContext::UpdateWebRenderCanvasData(
221 mozilla::nsDisplayListBuilder
* aBuilder
, WebRenderCanvasData
* aCanvasData
) {
222 auto* renderer
= aCanvasData
->GetCanvasRenderer();
224 if (renderer
&& mRemoteTextureOwnerId
.isSome() &&
225 renderer
->GetRemoteTextureOwnerIdOfPushCallback() ==
226 mRemoteTextureOwnerId
) {
230 renderer
= aCanvasData
->CreateCanvasRenderer();
231 if (!InitializeCanvasRenderer(aBuilder
, renderer
)) {
232 // Clear CanvasRenderer of WebRenderCanvasData
233 aCanvasData
->ClearCanvasRenderer();
239 bool CanvasContext::InitializeCanvasRenderer(
240 nsDisplayListBuilder
* aBuilder
, layers::CanvasRenderer
* aRenderer
) {
241 if (mRemoteTextureOwnerId
.isNothing()) {
245 layers::CanvasRendererData data
;
246 data
.mContext
= this;
247 data
.mSize
= mCanvasSize
;
248 data
.mIsOpaque
= false;
249 data
.mRemoteTextureOwnerIdOfPushCallback
= mRemoteTextureOwnerId
;
251 aRenderer
->Initialize(data
);
252 aRenderer
->SetDirty();
254 if (mWaitingCanvasRendererInitialized
) {
255 InvalidateCanvasContent();
257 mWaitingCanvasRendererInitialized
= false;
262 mozilla::UniquePtr
<uint8_t[]> CanvasContext::GetImageBuffer(
263 int32_t* out_format
, gfx::IntSize
* out_imageSize
) {
268 RefPtr
<gfx::SourceSurface
> snapshot
= GetSurfaceSnapshot(&any
);
273 RefPtr
<gfx::DataSourceSurface
> dataSurface
= snapshot
->GetDataSurface();
274 *out_imageSize
= dataSurface
->GetSize();
276 if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization
)) {
277 gfxUtils::GetImageBufferWithRandomNoise(
279 /* aIsAlphaPremultiplied */ true, GetCookieJarSettings(), &*out_format
);
282 return gfxUtils::GetImageBuffer(dataSurface
, /* aIsAlphaPremultiplied */ true,
286 NS_IMETHODIMP
CanvasContext::GetInputStream(const char* aMimeType
,
287 const nsAString
& aEncoderOptions
,
288 nsIInputStream
** aStream
) {
290 RefPtr
<gfx::SourceSurface
> snapshot
= GetSurfaceSnapshot(&any
);
292 return NS_ERROR_FAILURE
;
295 RefPtr
<gfx::DataSourceSurface
> dataSurface
= snapshot
->GetDataSurface();
297 if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization
)) {
298 gfxUtils::GetInputStreamWithRandomNoise(
299 dataSurface
, /* aIsAlphaPremultiplied */ true, aMimeType
,
300 aEncoderOptions
, GetCookieJarSettings(), aStream
);
303 return gfxUtils::GetInputStream(dataSurface
, /* aIsAlphaPremultiplied */ true,
304 aMimeType
, aEncoderOptions
, aStream
);
307 already_AddRefed
<mozilla::gfx::SourceSurface
> CanvasContext::GetSurfaceSnapshot(
308 gfxAlphaType
* aOutAlphaType
) {
310 *aOutAlphaType
= gfxAlphaType::Premult
;
313 auto* const cm
= gfx::CanvasManagerChild::Get();
318 if (!mBridge
|| !mBridge
->IsOpen() || mRemoteTextureOwnerId
.isNothing()) {
322 MOZ_ASSERT(mRemoteTextureOwnerId
.isSome());
323 return cm
->GetSnapshot(cm
->Id(), mBridge
->Id(), mRemoteTextureOwnerId
,
324 mGfxFormat
, /* aPremultiply */ false,
328 Maybe
<layers::SurfaceDescriptor
> CanvasContext::GetFrontBuffer(
329 WebGLFramebufferJS
*, const bool) {
330 // With canvas element, remote texture push callback pushes remote texture
331 // from RemoteTextureMap to WebRenderImageHost. With offscreen canvas, the
332 // push callback is not used. remote texture is notified from
333 // ShareableCanvasRenderer to WebRenderImageHost.
334 if (mPendingSwapChainPresent
) {
335 auto desc
= SwapChainPresent();
336 MOZ_ASSERT(!mPendingSwapChainPresent
);
342 void CanvasContext::ForceNewFrame() {
343 if (!mCanvasElement
&& !mOffscreenCanvas
) {
347 // Force a new frame to be built, which will execute the
348 // `CanvasContextType::WebGPU` switch case in `CreateWebRenderCommands` and
349 // populate the WR user data.
350 if (mCanvasElement
) {
351 mCanvasElement
->InvalidateCanvas();
352 } else if (mOffscreenCanvas
) {
353 dom::OffscreenCanvasDisplayData data
;
354 data
.mSize
= mCanvasSize
;
355 data
.mIsOpaque
= false;
356 data
.mOwnerId
= mRemoteTextureOwnerId
;
357 mOffscreenCanvas
->UpdateDisplayData(data
);
361 void CanvasContext::InvalidateCanvasContent() {
362 if (!mCanvasElement
&& !mOffscreenCanvas
) {
363 MOZ_ASSERT_UNREACHABLE("unexpected to be called");
367 if (mCanvasElement
) {
368 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement
);
369 mCanvasElement
->InvalidateCanvasContent(nullptr);
370 } else if (mOffscreenCanvas
) {
371 mOffscreenCanvas
->QueueCommitToCompositor();
375 } // namespace mozilla::webgpu