no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / webgpu / CanvasContext.cpp
blob749b27a8fadfa6c881eb04b3b6794474ac3d6e7f
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"
8 #include "gfxUtils.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"
22 namespace mozilla {
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) {
31 aField.UnlinkForCC();
34 // -
36 template <class T>
37 inline void ImplCycleCollectionTraverse(
38 nsCycleCollectionTraversalCallback& aCallback,
39 const std::unique_ptr<T>& aField, const char* aName, uint32_t aFlags) {
40 if (aField) {
41 ImplCycleCollectionTraverse(aCallback, *aField, aName, aFlags);
45 template <class T>
46 inline void ImplCycleCollectionUnlink(std::unique_ptr<T>& aField) {
47 aField = nullptr;
50 } // namespace mozilla
52 // -
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,
60 mTexture, mBridge,
61 mCanvasElement,
62 mOffscreenCanvas)
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)
68 NS_INTERFACE_MAP_END
70 // -
72 CanvasContext::CanvasContext() = default;
74 CanvasContext::~CanvasContext() {
75 Cleanup();
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);
86 // -
88 void CanvasContext::GetCanvas(
89 dom::OwningHTMLCanvasElementOrOffscreenCanvas& aRetVal) const {
90 if (mCanvasElement) {
91 aRetVal.SetAsHTMLCanvasElement() = mCanvasElement;
92 } else if (mOffscreenCanvas) {
93 aRetVal.SetAsOffscreenCanvas() = mOffscreenCanvas;
94 } else {
95 MOZ_CRASH(
96 "This should only happen briefly during CC Unlink, and no JS should "
97 "happen then.");
101 void CanvasContext::Configure(const dom::GPUCanvasConfiguration& aConfig) {
102 Unconfigure();
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;
111 break;
112 case dom::GPUTextureFormat::Bgra8unorm:
113 case dom::GPUTextureFormat::Bgra8unorm_srgb:
114 mGfxFormat = gfx::SurfaceFormat::B8G8R8A8;
115 break;
116 default:
117 NS_WARNING("Specified swap chain format is not supported");
118 return;
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);
130 if (!mTexture) {
131 Unconfigure();
132 return;
135 mTexture->mTargetContext = this;
136 mBridge = aConfig.mDevice->GetBridge();
137 if (mCanvasElement) {
138 mWaitingCanvasRendererInitialized = true;
141 ForceNewFrame();
144 void CanvasContext::Unconfigure() {
145 if (mBridge && mBridge->IsOpen() && mRemoteTextureOwnerId.isSome()) {
146 mBridge->SendSwapChainDrop(*mRemoteTextureOwnerId);
148 mRemoteTextureOwnerId = Nothing();
149 mBridge = nullptr;
150 mConfig = nullptr;
151 mTexture = nullptr;
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;
162 if (mConfig) {
163 const auto copy = dom::GPUCanvasConfiguration{
164 *mConfig}; // So we can't null it out on ourselves.
165 Configure(copy);
167 return NS_OK;
170 RefPtr<Texture> CanvasContext::GetCurrentTexture(ErrorResult& aRv) {
171 if (!mTexture) {
172 aRv.ThrowOperationError("Canvas not configured");
173 return nullptr;
176 MOZ_ASSERT(mConfig);
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;
186 return mTexture;
189 void CanvasContext::MaybeQueueSwapChainPresent() {
190 if (mPendingSwapChainPresent) {
191 return;
194 mPendingSwapChainPresent = true;
196 if (mWaitingCanvasRendererInitialized) {
197 return;
200 InvalidateCanvasContent();
203 Maybe<layers::SurfaceDescriptor> CanvasContext::SwapChainPresent() {
204 mPendingSwapChainPresent = false;
205 if (!mBridge || !mBridge->IsOpen() || mRemoteTextureOwnerId.isNothing() ||
206 !mTexture) {
207 return Nothing();
209 mLastRemoteTextureId = Some(layers::RemoteTextureId::GetNext());
210 mBridge->SwapChainPresent(mTexture->mId, *mLastRemoteTextureId,
211 *mRemoteTextureOwnerId);
212 if (mUseExternalTextureInSwapChain) {
213 mTexture->Destroy();
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) {
227 return true;
230 renderer = aCanvasData->CreateCanvasRenderer();
231 if (!InitializeCanvasRenderer(aBuilder, renderer)) {
232 // Clear CanvasRenderer of WebRenderCanvasData
233 aCanvasData->ClearCanvasRenderer();
234 return false;
236 return true;
239 bool CanvasContext::InitializeCanvasRenderer(
240 nsDisplayListBuilder* aBuilder, layers::CanvasRenderer* aRenderer) {
241 if (mRemoteTextureOwnerId.isNothing()) {
242 return false;
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;
259 return true;
262 mozilla::UniquePtr<uint8_t[]> CanvasContext::GetImageBuffer(
263 int32_t* out_format, gfx::IntSize* out_imageSize) {
264 *out_format = 0;
265 *out_imageSize = {};
267 gfxAlphaType any;
268 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
269 if (!snapshot) {
270 return nullptr;
273 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
274 *out_imageSize = dataSurface->GetSize();
276 if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
277 gfxUtils::GetImageBufferWithRandomNoise(
278 dataSurface,
279 /* aIsAlphaPremultiplied */ true, GetCookieJarSettings(), &*out_format);
282 return gfxUtils::GetImageBuffer(dataSurface, /* aIsAlphaPremultiplied */ true,
283 &*out_format);
286 NS_IMETHODIMP CanvasContext::GetInputStream(const char* aMimeType,
287 const nsAString& aEncoderOptions,
288 nsIInputStream** aStream) {
289 gfxAlphaType any;
290 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
291 if (!snapshot) {
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) {
309 if (aOutAlphaType) {
310 *aOutAlphaType = gfxAlphaType::Premult;
313 auto* const cm = gfx::CanvasManagerChild::Get();
314 if (!cm) {
315 return nullptr;
318 if (!mBridge || !mBridge->IsOpen() || mRemoteTextureOwnerId.isNothing()) {
319 return nullptr;
322 MOZ_ASSERT(mRemoteTextureOwnerId.isSome());
323 return cm->GetSnapshot(cm->Id(), mBridge->Id(), mRemoteTextureOwnerId,
324 mGfxFormat, /* aPremultiply */ false,
325 /* aYFlip */ 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);
337 return desc;
339 return Nothing();
342 void CanvasContext::ForceNewFrame() {
343 if (!mCanvasElement && !mOffscreenCanvas) {
344 return;
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");
364 return;
367 if (mCanvasElement) {
368 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
369 mCanvasElement->InvalidateCanvasContent(nullptr);
370 } else if (mOffscreenCanvas) {
371 mOffscreenCanvas->QueueCommitToCompositor();
375 } // namespace mozilla::webgpu