tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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