Bug 1857841 - pt 3. Add a new page kind named "fresh" r=glandium
[gecko.git] / dom / webgpu / CanvasContext.cpp
blob49f34196c4340ebf21b759b2a1705b8d2fa1f321
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/CompositableForwarder.h"
15 #include "mozilla/layers/ImageDataSerializer.h"
16 #include "mozilla/layers/LayersSurfaces.h"
17 #include "mozilla/layers/RenderRootStateManager.h"
18 #include "mozilla/layers/WebRenderCanvasRenderer.h"
19 #include "mozilla/StaticPrefs_privacy.h"
20 #include "mozilla/SVGObserverUtils.h"
21 #include "ipc/WebGPUChild.h"
22 #include "Utility.h"
24 namespace mozilla {
26 inline void ImplCycleCollectionTraverse(
27 nsCycleCollectionTraversalCallback& aCallback,
28 dom::GPUCanvasConfiguration& aField, const char* aName, uint32_t aFlags) {
29 aField.TraverseForCC(aCallback, aFlags);
32 inline void ImplCycleCollectionUnlink(dom::GPUCanvasConfiguration& aField) {
33 aField.UnlinkForCC();
36 // -
38 template <class T>
39 inline void ImplCycleCollectionTraverse(
40 nsCycleCollectionTraversalCallback& aCallback,
41 const std::unique_ptr<T>& aField, const char* aName, uint32_t aFlags) {
42 if (aField) {
43 ImplCycleCollectionTraverse(aCallback, *aField, aName, aFlags);
47 template <class T>
48 inline void ImplCycleCollectionUnlink(std::unique_ptr<T>& aField) {
49 aField = nullptr;
52 } // namespace mozilla
54 // -
56 namespace mozilla::webgpu {
58 NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasContext)
59 NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasContext)
61 GPU_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(CanvasContext, mConfig,
62 mTexture, mBridge,
63 mCanvasElement,
64 mOffscreenCanvas)
66 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasContext)
67 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
68 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
69 NS_INTERFACE_MAP_ENTRY(nsISupports)
70 NS_INTERFACE_MAP_END
72 // -
74 CanvasContext::CanvasContext() = default;
76 CanvasContext::~CanvasContext() {
77 Cleanup();
78 RemovePostRefreshObserver();
81 void CanvasContext::Cleanup() { Unconfigure(); }
83 JSObject* CanvasContext::WrapObject(JSContext* aCx,
84 JS::Handle<JSObject*> aGivenProto) {
85 return dom::GPUCanvasContext_Binding::Wrap(aCx, this, aGivenProto);
88 // -
90 void CanvasContext::GetCanvas(
91 dom::OwningHTMLCanvasElementOrOffscreenCanvas& aRetVal) const {
92 if (mCanvasElement) {
93 aRetVal.SetAsHTMLCanvasElement() = mCanvasElement;
94 } else if (mOffscreenCanvas) {
95 aRetVal.SetAsOffscreenCanvas() = mOffscreenCanvas;
96 } else {
97 MOZ_CRASH(
98 "This should only happen briefly during CC Unlink, and no JS should "
99 "happen then.");
103 void CanvasContext::Configure(const dom::GPUCanvasConfiguration& aConfig) {
104 Unconfigure();
106 // Bug 1864904: Failures in validation should throw a TypeError, per spec.
108 // these formats are guaranteed by the spec
109 switch (aConfig.mFormat) {
110 case dom::GPUTextureFormat::Rgba8unorm:
111 case dom::GPUTextureFormat::Rgba8unorm_srgb:
112 mGfxFormat = gfx::SurfaceFormat::R8G8B8A8;
113 break;
114 case dom::GPUTextureFormat::Bgra8unorm:
115 case dom::GPUTextureFormat::Bgra8unorm_srgb:
116 mGfxFormat = gfx::SurfaceFormat::B8G8R8A8;
117 break;
118 default:
119 NS_WARNING("Specified swap chain format is not supported");
120 return;
123 mConfig.reset(new dom::GPUCanvasConfiguration(aConfig));
124 mRemoteTextureOwnerId = Some(layers::RemoteTextureOwnerId::GetNext());
125 mUseExternalTextureInSwapChain =
126 wgpu_client_use_external_texture_in_swapChain(
127 aConfig.mDevice->mId, ConvertTextureFormat(aConfig.mFormat));
128 mTexture = aConfig.mDevice->InitSwapChain(
129 mConfig.get(), mRemoteTextureOwnerId.ref(),
130 mUseExternalTextureInSwapChain, mGfxFormat, mCanvasSize);
131 if (!mTexture) {
132 Unconfigure();
133 return;
136 mTexture->mTargetContext = this;
137 mBridge = aConfig.mDevice->GetBridge();
138 if (mCanvasElement) {
139 mWaitingCanvasRendererInitialized = true;
142 ForceNewFrame();
145 void CanvasContext::Unconfigure() {
146 if (mBridge && mBridge->IsOpen() && mRemoteTextureOwnerId) {
147 mBridge->SendSwapChainDrop(
148 *mRemoteTextureOwnerId,
149 layers::ToRemoteTextureTxnType(mFwdTransactionTracker),
150 layers::ToRemoteTextureTxnId(mFwdTransactionTracker));
152 mRemoteTextureOwnerId = Nothing();
153 mFwdTransactionTracker = nullptr;
154 mBridge = nullptr;
155 mConfig = nullptr;
156 mTexture = nullptr;
157 mGfxFormat = gfx::SurfaceFormat::UNKNOWN;
160 NS_IMETHODIMP CanvasContext::SetDimensions(int32_t aWidth, int32_t aHeight) {
161 aWidth = std::max(1, aWidth);
162 aHeight = std::max(1, aHeight);
163 const auto newSize = gfx::IntSize{aWidth, aHeight};
164 if (newSize == mCanvasSize) return NS_OK; // No-op no-change resizes.
166 mCanvasSize = newSize;
167 if (mConfig) {
168 const auto copy = dom::GPUCanvasConfiguration{
169 *mConfig}; // So we can't null it out on ourselves.
170 Configure(copy);
172 return NS_OK;
175 RefPtr<Texture> CanvasContext::GetCurrentTexture(ErrorResult& aRv) {
176 if (!mTexture) {
177 aRv.ThrowOperationError("Canvas not configured");
178 return nullptr;
181 MOZ_ASSERT(mConfig);
182 MOZ_ASSERT(mRemoteTextureOwnerId.isSome());
184 if (mNewTextureRequested) {
185 mNewTextureRequested = false;
187 mTexture = mConfig->mDevice->CreateTextureForSwapChain(
188 mConfig.get(), mCanvasSize, mRemoteTextureOwnerId.ref());
189 mTexture->mTargetContext = this;
191 return mTexture;
194 void CanvasContext::MaybeQueueSwapChainPresent() {
195 MOZ_ASSERT(mTexture);
197 if (mTexture) {
198 mBridge->NotifyWaitForSubmit(mTexture->mId);
201 if (mPendingSwapChainPresent) {
202 return;
205 mPendingSwapChainPresent = true;
207 if (mWaitingCanvasRendererInitialized) {
208 return;
211 InvalidateCanvasContent();
214 Maybe<layers::SurfaceDescriptor> CanvasContext::SwapChainPresent() {
215 mPendingSwapChainPresent = false;
216 if (!mBridge || !mBridge->IsOpen() || mRemoteTextureOwnerId.isNothing() ||
217 !mTexture) {
218 return Nothing();
220 mLastRemoteTextureId = Some(layers::RemoteTextureId::GetNext());
221 mBridge->SwapChainPresent(mTexture->mId, *mLastRemoteTextureId,
222 *mRemoteTextureOwnerId);
223 if (mUseExternalTextureInSwapChain) {
224 mTexture->Destroy();
225 mNewTextureRequested = true;
227 return Some(layers::SurfaceDescriptorRemoteTexture(*mLastRemoteTextureId,
228 *mRemoteTextureOwnerId));
231 bool CanvasContext::UpdateWebRenderCanvasData(
232 mozilla::nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
233 auto* renderer = aCanvasData->GetCanvasRenderer();
235 if (renderer && mRemoteTextureOwnerId.isSome() &&
236 renderer->GetRemoteTextureOwnerId() == mRemoteTextureOwnerId) {
237 return true;
240 renderer = aCanvasData->CreateCanvasRenderer();
241 if (!InitializeCanvasRenderer(aBuilder, renderer)) {
242 // Clear CanvasRenderer of WebRenderCanvasData
243 aCanvasData->ClearCanvasRenderer();
244 return false;
246 return true;
249 bool CanvasContext::InitializeCanvasRenderer(
250 nsDisplayListBuilder* aBuilder, layers::CanvasRenderer* aRenderer) {
251 if (mRemoteTextureOwnerId.isNothing()) {
252 return false;
255 layers::CanvasRendererData data;
256 data.mContext = this;
257 data.mSize = mCanvasSize;
258 data.mIsOpaque = false;
259 data.mRemoteTextureOwnerId = mRemoteTextureOwnerId;
261 aRenderer->Initialize(data);
262 aRenderer->SetDirty();
264 if (mWaitingCanvasRendererInitialized) {
265 InvalidateCanvasContent();
267 mWaitingCanvasRendererInitialized = false;
269 return true;
272 mozilla::UniquePtr<uint8_t[]> CanvasContext::GetImageBuffer(
273 int32_t* out_format, gfx::IntSize* out_imageSize) {
274 *out_format = 0;
275 *out_imageSize = {};
277 gfxAlphaType any;
278 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
279 if (!snapshot) {
280 return nullptr;
283 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
284 *out_imageSize = dataSurface->GetSize();
286 if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
287 gfxUtils::GetImageBufferWithRandomNoise(
288 dataSurface,
289 /* aIsAlphaPremultiplied */ true, GetCookieJarSettings(), &*out_format);
292 return gfxUtils::GetImageBuffer(dataSurface, /* aIsAlphaPremultiplied */ true,
293 &*out_format);
296 NS_IMETHODIMP CanvasContext::GetInputStream(const char* aMimeType,
297 const nsAString& aEncoderOptions,
298 nsIInputStream** aStream) {
299 gfxAlphaType any;
300 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
301 if (!snapshot) {
302 return NS_ERROR_FAILURE;
305 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
307 if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
308 gfxUtils::GetInputStreamWithRandomNoise(
309 dataSurface, /* aIsAlphaPremultiplied */ true, aMimeType,
310 aEncoderOptions, GetCookieJarSettings(), aStream);
313 return gfxUtils::GetInputStream(dataSurface, /* aIsAlphaPremultiplied */ true,
314 aMimeType, aEncoderOptions, aStream);
317 already_AddRefed<mozilla::gfx::SourceSurface> CanvasContext::GetSurfaceSnapshot(
318 gfxAlphaType* aOutAlphaType) {
319 if (aOutAlphaType) {
320 *aOutAlphaType = gfxAlphaType::Premult;
323 auto* const cm = gfx::CanvasManagerChild::Get();
324 if (!cm) {
325 return nullptr;
328 if (!mBridge || !mBridge->IsOpen() || mRemoteTextureOwnerId.isNothing()) {
329 return nullptr;
332 MOZ_ASSERT(mRemoteTextureOwnerId.isSome());
333 return cm->GetSnapshot(cm->Id(), mBridge->Id(), mRemoteTextureOwnerId,
334 mGfxFormat, /* aPremultiply */ false,
335 /* aYFlip */ false);
338 Maybe<layers::SurfaceDescriptor> CanvasContext::GetFrontBuffer(
339 WebGLFramebufferJS*, const bool) {
340 if (mPendingSwapChainPresent) {
341 auto desc = SwapChainPresent();
342 MOZ_ASSERT(!mPendingSwapChainPresent);
343 return desc;
345 return Nothing();
348 already_AddRefed<layers::FwdTransactionTracker>
349 CanvasContext::UseCompositableForwarder(
350 layers::CompositableForwarder* aForwarder) {
351 return layers::FwdTransactionTracker::GetOrCreate(mFwdTransactionTracker);
354 void CanvasContext::ForceNewFrame() {
355 if (!mCanvasElement && !mOffscreenCanvas) {
356 return;
359 // Force a new frame to be built, which will execute the
360 // `CanvasContextType::WebGPU` switch case in `CreateWebRenderCommands` and
361 // populate the WR user data.
362 if (mCanvasElement) {
363 mCanvasElement->InvalidateCanvas();
364 } else if (mOffscreenCanvas) {
365 dom::OffscreenCanvasDisplayData data;
366 data.mSize = mCanvasSize;
367 data.mIsOpaque = false;
368 data.mOwnerId = mRemoteTextureOwnerId;
369 mOffscreenCanvas->UpdateDisplayData(data);
373 void CanvasContext::InvalidateCanvasContent() {
374 if (!mCanvasElement && !mOffscreenCanvas) {
375 MOZ_ASSERT_UNREACHABLE("unexpected to be called");
376 return;
379 if (mCanvasElement) {
380 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
381 mCanvasElement->InvalidateCanvasContent(nullptr);
382 } else if (mOffscreenCanvas) {
383 mOffscreenCanvas->QueueCommitToCompositor();
387 } // namespace mozilla::webgpu