CanvasContext.cpp (16258B)
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/. */ 5 6 #include "CanvasContext.h" 7 8 #include "LayerUserData.h" 9 #include "Utility.h" 10 #include "gfxUtils.h" 11 #include "ipc/WebGPUChild.h" 12 #include "mozilla/ProfilerMarkers.h" 13 #include "mozilla/SVGObserverUtils.h" 14 #include "mozilla/StaticPrefs_privacy.h" 15 #include "mozilla/dom/HTMLCanvasElement.h" 16 #include "mozilla/dom/WebGPUBinding.h" 17 #include "mozilla/gfx/2D.h" 18 #include "mozilla/gfx/CanvasManagerChild.h" 19 #include "mozilla/gfx/Logging.h" 20 #include "mozilla/gfx/gfxVars.h" 21 #include "mozilla/layers/CanvasRenderer.h" 22 #include "mozilla/layers/CompositableForwarder.h" 23 #include "mozilla/layers/ImageDataSerializer.h" 24 #include "mozilla/layers/LayersSurfaces.h" 25 #include "mozilla/layers/RenderRootStateManager.h" 26 #include "mozilla/layers/WebRenderCanvasRenderer.h" 27 #include "nsDisplayList.h" 28 29 namespace mozilla { 30 31 inline void ImplCycleCollectionTraverse( 32 nsCycleCollectionTraversalCallback& aCallback, 33 dom::GPUCanvasConfiguration& aField, const char* aName, uint32_t aFlags) { 34 aField.TraverseForCC(aCallback, aFlags); 35 } 36 37 inline void ImplCycleCollectionUnlink(dom::GPUCanvasConfiguration& aField) { 38 aField.UnlinkForCC(); 39 } 40 41 } // namespace mozilla 42 43 // - 44 45 namespace mozilla::webgpu { 46 47 NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasContext) 48 NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasContext) 49 50 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(CanvasContext, mConfiguration, 51 mCurrentTexture, mCanvasElement, 52 mOffscreenCanvas) 53 54 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasContext) 55 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 56 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal) 57 NS_INTERFACE_MAP_ENTRY(nsISupports) 58 NS_INTERFACE_MAP_END 59 60 // - 61 62 CanvasContext::CanvasContext() = default; 63 64 CanvasContext::~CanvasContext() { 65 Unconfigure(); 66 RemovePostRefreshObserver(); 67 } 68 69 JSObject* CanvasContext::WrapObject(JSContext* aCx, 70 JS::Handle<JSObject*> aGivenProto) { 71 return dom::GPUCanvasContext_Binding::Wrap(aCx, this, aGivenProto); 72 } 73 74 // - 75 76 void CanvasContext::GetCanvas( 77 dom::OwningHTMLCanvasElementOrOffscreenCanvas& aRetVal) const { 78 if (mCanvasElement) { 79 aRetVal.SetAsHTMLCanvasElement() = mCanvasElement; 80 } else if (mOffscreenCanvas) { 81 aRetVal.SetAsOffscreenCanvas() = mOffscreenCanvas; 82 } else { 83 MOZ_CRASH( 84 "This should only happen briefly during CC Unlink, and no JS should " 85 "happen then."); 86 } 87 } 88 89 // Note: `SetDimensions` assumes it can ignore this `ErrorResult` because the 90 // format is already validated. Revisit if adding other error cases. 91 void CanvasContext::Configure(const dom::GPUCanvasConfiguration& aConfig, 92 ErrorResult& aRv) { 93 Unconfigure(); 94 95 // Only the three formats explicitly listed are permitted here (one of which 96 // is not yet supported). 97 // https://www.w3.org/TR/webgpu/#supported-context-formats 98 switch (aConfig.mFormat) { 99 case dom::GPUTextureFormat::Rgba8unorm: 100 mGfxFormat = gfx::SurfaceFormat::R8G8B8A8; 101 break; 102 case dom::GPUTextureFormat::Bgra8unorm: 103 mGfxFormat = gfx::SurfaceFormat::B8G8R8A8; 104 break; 105 case dom::GPUTextureFormat::Rgba16float: 106 aRv.ThrowTypeError( 107 "Canvas texture format `rgba16float` is not yet supported. " 108 "Subscribe to <https://bugzilla.mozilla.org/show_bug.cgi?id=1834395>" 109 " for updates on its development in Firefox."); 110 return; 111 default: 112 aRv.ThrowTypeError( 113 nsPrintfCString("`%s` is not a supported context format.", 114 dom::GetEnumString(aConfig.mFormat).get())); 115 return; 116 } 117 118 mConfiguration.reset(new dom::GPUCanvasConfiguration(aConfig)); 119 mRemoteTextureOwnerId = Some(layers::RemoteTextureOwnerId::GetNext()); 120 mUseSharedTextureInSwapChain = 121 aConfig.mDevice->mSupportSharedTextureInSwapChain; 122 if (mUseSharedTextureInSwapChain) { 123 bool client_can_use = wgpu_client_use_shared_texture_in_swapChain( 124 ConvertTextureFormat(aConfig.mFormat)); 125 if (!client_can_use) { 126 gfxCriticalNote << "WebGPU: disabling SharedTexture swapchain: \n" 127 "canvas configuration format not supported"; 128 mUseSharedTextureInSwapChain = false; 129 } 130 } 131 if (!gfx::gfxVars::AllowWebGPUPresentWithoutReadback()) { 132 gfxCriticalNote 133 << "WebGPU: disabling SharedTexture swapchain: \n" 134 "`dom.webgpu.allow-present-without-readback` pref is false"; 135 mUseSharedTextureInSwapChain = false; 136 } 137 #ifdef XP_WIN 138 // When WebRender does not use hardware acceleration, disable shared texture 139 // in swap chain. Since compositor device might not exist. 140 if (gfx::gfxVars::UseSoftwareWebRender() && 141 !gfx::gfxVars::AllowSoftwareWebRenderD3D11()) { 142 gfxCriticalNote << "WebGPU: disabling SharedTexture swapchain: \n" 143 "WebRender is not using hardware acceleration"; 144 mUseSharedTextureInSwapChain = false; 145 } 146 #elif defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) 147 // When DMABufDevice is not enabled, disable shared texture in swap chain. 148 const auto& modifiers = gfx::gfxVars::DMABufModifiersARGB(); 149 if (modifiers.IsEmpty()) { 150 gfxCriticalNote << "WebGPU: disabling SharedTexture swapchain: \n" 151 "missing GBM_FORMAT_ARGB8888 dmabuf format"; 152 mUseSharedTextureInSwapChain = false; 153 } 154 #endif 155 156 mCurrentTexture = aConfig.mDevice->InitSwapChain( 157 mConfiguration.get(), mRemoteTextureOwnerId.ref(), 158 mUseSharedTextureInSwapChain, mGfxFormat, mCanvasSize); 159 if (!mCurrentTexture) { 160 Unconfigure(); 161 return; 162 } 163 164 mCurrentTexture->mTargetContext = this; 165 mChild = aConfig.mDevice->GetChild(); 166 if (mCanvasElement) { 167 mWaitingCanvasRendererInitialized = true; 168 } 169 170 ForceNewFrame(); 171 } 172 173 void CanvasContext::Unconfigure() { 174 if (mChild && mChild->CanSend() && mRemoteTextureOwnerId) { 175 auto txn_type = layers::ToRemoteTextureTxnType(mFwdTransactionTracker); 176 auto txn_id = layers::ToRemoteTextureTxnId(mFwdTransactionTracker); 177 ffi::wgpu_client_swap_chain_drop( 178 mChild->GetClient(), mRemoteTextureOwnerId->mId, txn_type, txn_id); 179 } 180 mRemoteTextureOwnerId = Nothing(); 181 mFwdTransactionTracker = nullptr; 182 mChild = nullptr; 183 mConfiguration = nullptr; 184 mCurrentTexture = nullptr; 185 mGfxFormat = gfx::SurfaceFormat::UNKNOWN; 186 } 187 188 NS_IMETHODIMP CanvasContext::SetDimensions(int32_t aWidth, int32_t aHeight) { 189 const auto newSize = gfx::IntSize{aWidth, aHeight}; 190 if (newSize == mCanvasSize) return NS_OK; // No-op no-change resizes. 191 192 mCanvasSize = newSize; 193 if (mConfiguration) { 194 const auto copy = dom::GPUCanvasConfiguration{ 195 *mConfiguration}; // So we can't null it out on ourselves. 196 // The format in `mConfiguration` was already validated, we won't get an 197 // error here. 198 Configure(copy, IgnoredErrorResult()); 199 } 200 return NS_OK; 201 } 202 203 void CanvasContext::GetConfiguration( 204 dom::Nullable<dom::GPUCanvasConfiguration>& aRv) { 205 if (mConfiguration) { 206 aRv.SetValue(*mConfiguration); 207 } else { 208 aRv.SetNull(); 209 } 210 } 211 212 RefPtr<Texture> CanvasContext::GetCurrentTexture(ErrorResult& aRv) { 213 if (!mCurrentTexture) { 214 aRv.ThrowInvalidStateError("Canvas not configured"); 215 return nullptr; 216 } 217 218 MOZ_ASSERT(mConfiguration); 219 MOZ_ASSERT(mRemoteTextureOwnerId.isSome()); 220 221 if (mNewTextureRequested) { 222 mNewTextureRequested = false; 223 224 mCurrentTexture = mConfiguration->mDevice->CreateTextureForSwapChain( 225 mConfiguration.get(), mCanvasSize, mRemoteTextureOwnerId.ref()); 226 mCurrentTexture->mTargetContext = this; 227 } 228 return mCurrentTexture; 229 } 230 231 void CanvasContext::MaybeQueueSwapChainPresent() { 232 if (!mConfiguration) { 233 return; 234 } 235 236 MOZ_ASSERT(mCurrentTexture); 237 238 if (mCurrentTexture) { 239 mChild->NotifyWaitForSubmit(mCurrentTexture->GetId()); 240 } 241 242 if (mPendingSwapChainPresent) { 243 return; 244 } 245 246 mPendingSwapChainPresent = true; 247 248 if (mWaitingCanvasRendererInitialized) { 249 return; 250 } 251 252 InvalidateCanvasContent(); 253 } 254 255 Maybe<layers::SurfaceDescriptor> CanvasContext::SwapChainPresent() { 256 mPendingSwapChainPresent = false; 257 if (!mChild || !mChild->CanSend() || mRemoteTextureOwnerId.isNothing() || 258 !mCurrentTexture) { 259 return Nothing(); 260 } 261 262 if (mCanvasElement) { 263 JSObject* obj = mCanvasElement->GetWrapper(); 264 if (obj) { 265 dom::SetUseCounter(obj, eUseCounter_custom_WebgpuRenderOutput); 266 } 267 } 268 269 mLastRemoteTextureId = Some(layers::RemoteTextureId::GetNext()); 270 mChild->SwapChainPresent(mCurrentTexture->GetId(), *mLastRemoteTextureId, 271 *mRemoteTextureOwnerId); 272 if (mUseSharedTextureInSwapChain) { 273 mCurrentTexture->Destroy(); 274 mNewTextureRequested = true; 275 } 276 277 PROFILER_MARKER_UNTYPED("WebGPU: SwapChainPresent", GRAPHICS_WebGPU); 278 mChild->FlushQueuedMessages(); 279 280 return Some(layers::SurfaceDescriptorRemoteTexture(*mLastRemoteTextureId, 281 *mRemoteTextureOwnerId)); 282 } 283 284 bool CanvasContext::UpdateWebRenderCanvasData( 285 mozilla::nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) { 286 auto* renderer = aCanvasData->GetCanvasRenderer(); 287 288 if (renderer && mRemoteTextureOwnerId.isSome() && 289 renderer->GetRemoteTextureOwnerId() == mRemoteTextureOwnerId) { 290 return true; 291 } 292 293 renderer = aCanvasData->CreateCanvasRenderer(); 294 if (!InitializeCanvasRenderer(aBuilder, renderer)) { 295 // Clear CanvasRenderer of WebRenderCanvasData 296 aCanvasData->ClearCanvasRenderer(); 297 return false; 298 } 299 return true; 300 } 301 302 bool CanvasContext::InitializeCanvasRenderer( 303 nsDisplayListBuilder* aBuilder, layers::CanvasRenderer* aRenderer) { 304 if (mRemoteTextureOwnerId.isNothing()) { 305 return false; 306 } 307 308 layers::CanvasRendererData data; 309 data.mContext = this; 310 data.mSize = mCanvasSize; 311 data.mIsOpaque = false; 312 data.mRemoteTextureOwnerId = mRemoteTextureOwnerId; 313 314 aRenderer->Initialize(data); 315 aRenderer->SetDirty(); 316 317 if (mWaitingCanvasRendererInitialized) { 318 InvalidateCanvasContent(); 319 } 320 mWaitingCanvasRendererInitialized = false; 321 322 return true; 323 } 324 325 mozilla::UniquePtr<uint8_t[]> CanvasContext::GetImageBuffer( 326 mozilla::CanvasUtils::ImageExtraction aExtractionBehavior, 327 int32_t* out_format, gfx::IntSize* out_imageSize) { 328 *out_format = 0; 329 *out_imageSize = {}; 330 331 gfxAlphaType any; 332 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any); 333 if (!snapshot) { 334 return nullptr; 335 } 336 337 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface(); 338 *out_imageSize = dataSurface->GetSize(); 339 340 nsRFPService::PotentiallyDumpImage(PrincipalOrNull(), dataSurface); 341 if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) { 342 gfxUtils::GetImageBufferWithRandomNoise(dataSurface, 343 /* aIsAlphaPremultiplied */ true, 344 GetCookieJarSettings(), 345 PrincipalOrNull(), &*out_format); 346 } 347 348 return gfxUtils::GetImageBuffer(dataSurface, /* aIsAlphaPremultiplied */ true, 349 &*out_format); 350 } 351 352 NS_IMETHODIMP CanvasContext::GetInputStream( 353 const char* aMimeType, const nsAString& aEncoderOptions, 354 mozilla::CanvasUtils::ImageExtraction aExtractionBehavior, 355 const nsACString& aRandomizationKey, nsIInputStream** aStream) { 356 gfxAlphaType any; 357 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any); 358 if (!snapshot) { 359 return NS_ERROR_FAILURE; 360 } 361 362 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface(); 363 364 nsRFPService::PotentiallyDumpImage(PrincipalOrNull(), dataSurface); 365 if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) { 366 return gfxUtils::GetInputStreamWithRandomNoise( 367 dataSurface, /* aIsAlphaPremultiplied */ true, aMimeType, 368 aEncoderOptions, GetCookieJarSettings(), PrincipalOrNull(), aStream); 369 } 370 371 return gfxUtils::GetInputStream(dataSurface, /* aIsAlphaPremultiplied */ true, 372 aMimeType, aEncoderOptions, aStream); 373 } 374 375 bool CanvasContext::GetIsOpaque() { 376 if (!mConfiguration) { 377 return false; 378 } 379 return mConfiguration->mAlphaMode == dom::GPUCanvasAlphaMode::Opaque; 380 } 381 382 already_AddRefed<gfx::SourceSurface> CanvasContext::GetSurfaceSnapshot( 383 gfxAlphaType* aOutAlphaType) { 384 const bool isOpaque = GetIsOpaque(); 385 gfx::SurfaceFormat snapshotFormat = mGfxFormat; 386 if (isOpaque) { 387 if (aOutAlphaType) { 388 *aOutAlphaType = gfxAlphaType::Opaque; 389 } 390 if (mGfxFormat == gfx::SurfaceFormat::B8G8R8A8) { 391 snapshotFormat = gfx::SurfaceFormat::B8G8R8X8; 392 } else if (mGfxFormat == gfx::SurfaceFormat::R8G8B8A8) { 393 snapshotFormat = gfx::SurfaceFormat::R8G8B8X8; 394 } 395 } else { 396 if (aOutAlphaType) { 397 *aOutAlphaType = gfxAlphaType::Premult; 398 } 399 } 400 401 auto* const cm = gfx::CanvasManagerChild::Get(); 402 if (!cm) { 403 return nullptr; 404 } 405 406 if (!mChild || !mChild->CanSend() || mRemoteTextureOwnerId.isNothing()) { 407 return nullptr; 408 } 409 410 MOZ_ASSERT(mRemoteTextureOwnerId.isSome()); 411 412 // The parent side needs to create a command encoder which will be submitted 413 // and dropped right away so we create and release an encoder ID here. 414 RawId commandEncoderId = 415 ffi::wgpu_client_make_command_encoder_id(mChild->GetClient()); 416 RawId commandBufferId = 417 ffi::wgpu_client_make_command_buffer_id(mChild->GetClient()); 418 RefPtr<gfx::DataSourceSurface> snapshot = cm->GetSnapshot( 419 cm->Id(), mChild->Id(), mRemoteTextureOwnerId, Some(commandEncoderId), 420 Some(commandBufferId), snapshotFormat, /* aPremultiply */ false, 421 /* aYFlip */ false); 422 ffi::wgpu_client_free_command_encoder_id(mChild->GetClient(), 423 commandEncoderId); 424 ffi::wgpu_client_free_command_buffer_id(mChild->GetClient(), commandBufferId); 425 if (!snapshot) { 426 return nullptr; 427 } 428 429 // Clear alpha channel to 0xFF / 1.0 for opaque contexts. 430 // https://www.w3.org/TR/webgpu/#abstract-opdef-get-a-copy-of-the-image-contents-of-a-context 431 if (isOpaque) { 432 gfx::DataSourceSurface::ScopedMap map(snapshot, 433 gfx::DataSourceSurface::WRITE); 434 if (!map.IsMapped()) { 435 return nullptr; 436 } 437 438 for (int32_t y = 0; y < snapshot->GetSize().height; y++) { 439 for (int32_t x = 0; x < snapshot->GetSize().width; x++) { 440 uint8_t* const pixel = map.GetData() + y * map.GetStride() + x * 4; 441 pixel[3] = 0xFF; 442 } 443 } 444 } 445 446 return snapshot.forget(); 447 } 448 449 Maybe<layers::SurfaceDescriptor> CanvasContext::GetFrontBuffer( 450 WebGLFramebufferJS*, const bool) { 451 if (mPendingSwapChainPresent) { 452 auto desc = SwapChainPresent(); 453 MOZ_ASSERT(!mPendingSwapChainPresent); 454 return desc; 455 } 456 return Nothing(); 457 } 458 459 already_AddRefed<layers::FwdTransactionTracker> 460 CanvasContext::UseCompositableForwarder( 461 layers::CompositableForwarder* aForwarder) { 462 return layers::FwdTransactionTracker::GetOrCreate(mFwdTransactionTracker); 463 } 464 465 void CanvasContext::ForceNewFrame() { 466 if (!mCanvasElement && !mOffscreenCanvas) { 467 return; 468 } 469 470 // Force a new frame to be built, which will execute the 471 // `CanvasContextType::WebGPU` switch case in `CreateWebRenderCommands` and 472 // populate the WR user data. 473 if (mCanvasElement) { 474 mCanvasElement->InvalidateCanvas(); 475 } else if (mOffscreenCanvas) { 476 dom::OffscreenCanvasDisplayData data; 477 data.mSize = mCanvasSize; 478 data.mIsOpaque = false; 479 mOffscreenCanvas->UpdateDisplayData(data); 480 } 481 } 482 483 void CanvasContext::InvalidateCanvasContent() { 484 if (!mCanvasElement && !mOffscreenCanvas) { 485 MOZ_ASSERT_UNREACHABLE("unexpected to be called"); 486 return; 487 } 488 489 if (mCanvasElement) { 490 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); 491 mCanvasElement->InvalidateCanvasContent(nullptr); 492 } else if (mOffscreenCanvas) { 493 mOffscreenCanvas->QueueCommitToCompositor(); 494 } 495 } 496 497 } // namespace mozilla::webgpu