gecko-dev/dom/webgpu/CanvasContext.cpp

331 строка
9.9 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/WebGPUBinding.h"
#include "CanvasContext.h"
#include "gfxUtils.h"
#include "LayerUserData.h"
#include "nsDisplayList.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/gfx/CanvasManagerChild.h"
#include "mozilla/layers/CanvasRenderer.h"
#include "mozilla/layers/ImageDataSerializer.h"
#include "mozilla/layers/LayersSurfaces.h"
#include "mozilla/layers/RenderRootStateManager.h"
#include "mozilla/layers/WebRenderCanvasRenderer.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "ipc/WebGPUChild.h"
namespace mozilla {
inline void ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback,
dom::GPUCanvasConfiguration& aField, const char* aName, uint32_t aFlags) {
aField.TraverseForCC(aCallback, aFlags);
}
inline void ImplCycleCollectionUnlink(dom::GPUCanvasConfiguration& aField) {
aField.UnlinkForCC();
}
// -
template <class T>
inline void ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback,
const std::unique_ptr<T>& aField, const char* aName, uint32_t aFlags) {
if (aField) {
ImplCycleCollectionTraverse(aCallback, *aField, aName, aFlags);
}
}
template <class T>
inline void ImplCycleCollectionUnlink(std::unique_ptr<T>& aField) {
aField = nullptr;
}
} // namespace mozilla
// -
namespace mozilla::webgpu {
NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasContext)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasContext)
GPU_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(CanvasContext, mConfig,
mTexture, mBridge,
mCanvasElement,
mOffscreenCanvas)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasContext)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
// -
CanvasContext::CanvasContext() = default;
CanvasContext::~CanvasContext() {
Cleanup();
RemovePostRefreshObserver();
}
void CanvasContext::Cleanup() { Unconfigure(); }
JSObject* CanvasContext::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return dom::GPUCanvasContext_Binding::Wrap(aCx, this, aGivenProto);
}
// -
void CanvasContext::GetCanvas(
dom::OwningHTMLCanvasElementOrOffscreenCanvas& aRetVal) const {
if (mCanvasElement) {
aRetVal.SetAsHTMLCanvasElement() = mCanvasElement;
} else if (mOffscreenCanvas) {
aRetVal.SetAsOffscreenCanvas() = mOffscreenCanvas;
} else {
MOZ_CRASH(
"This should only happen briefly during CC Unlink, and no JS should "
"happen then.");
}
}
void CanvasContext::Configure(const dom::GPUCanvasConfiguration& aConfig) {
Unconfigure();
// these formats are guaranteed by the spec
switch (aConfig.mFormat) {
case dom::GPUTextureFormat::Rgba8unorm:
case dom::GPUTextureFormat::Rgba8unorm_srgb:
mGfxFormat = gfx::SurfaceFormat::R8G8B8A8;
break;
case dom::GPUTextureFormat::Bgra8unorm:
case dom::GPUTextureFormat::Bgra8unorm_srgb:
mGfxFormat = gfx::SurfaceFormat::B8G8R8A8;
break;
default:
NS_WARNING("Specified swap chain format is not supported");
return;
}
mConfig.reset(new dom::GPUCanvasConfiguration(aConfig));
mRemoteTextureOwnerId = Some(layers::RemoteTextureOwnerId::GetNext());
mUseExternalTextureInSwapChain =
wgpu_client_use_external_texture_in_swapChain(
aConfig.mDevice->mId,
WebGPUChild::ConvertTextureFormat(aConfig.mFormat));
mTexture = aConfig.mDevice->InitSwapChain(
mConfig.get(), mRemoteTextureOwnerId.ref(),
mUseExternalTextureInSwapChain, mGfxFormat, mCanvasSize);
if (!mTexture) {
Unconfigure();
return;
}
mTexture->mTargetContext = this;
mBridge = aConfig.mDevice->GetBridge();
ForceNewFrame();
}
void CanvasContext::Unconfigure() {
if (mBridge && mBridge->IsOpen() && mRemoteTextureOwnerId.isSome()) {
mBridge->SendSwapChainDestroy(*mRemoteTextureOwnerId);
}
mRemoteTextureOwnerId = Nothing();
mBridge = nullptr;
mConfig = nullptr;
mTexture = nullptr;
mGfxFormat = gfx::SurfaceFormat::UNKNOWN;
}
NS_IMETHODIMP CanvasContext::SetDimensions(int32_t aWidth, int32_t aHeight) {
aWidth = std::max(1, aWidth);
aHeight = std::max(1, aHeight);
const auto newSize = gfx::IntSize{aWidth, aHeight};
if (newSize == mCanvasSize) return NS_OK; // No-op no-change resizes.
mCanvasSize = newSize;
if (mConfig) {
const auto copy = dom::GPUCanvasConfiguration{
*mConfig}; // So we can't null it out on ourselves.
Configure(copy);
}
return NS_OK;
}
RefPtr<Texture> CanvasContext::GetCurrentTexture(ErrorResult& aRv) {
if (!mTexture) {
aRv.ThrowOperationError("Canvas not configured");
return nullptr;
}
MOZ_ASSERT(mConfig);
MOZ_ASSERT(mRemoteTextureOwnerId.isSome());
if (mNewTextureRequested) {
mNewTextureRequested = false;
mTexture = mConfig->mDevice->CreateTextureForSwapChain(
mConfig.get(), mCanvasSize, mRemoteTextureOwnerId.ref());
mTexture->mTargetContext = this;
}
return mTexture;
}
void CanvasContext::MaybeQueueSwapChainPresent() {
if (mPendingSwapChainPresent) {
return;
}
mPendingSwapChainPresent = true;
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(
NewCancelableRunnableMethod("CanvasContext::SwapChainPresent", this,
&CanvasContext::SwapChainPresent)));
}
void CanvasContext::SwapChainPresent() {
mPendingSwapChainPresent = false;
if (!mBridge || !mBridge->IsOpen() || mRemoteTextureOwnerId.isNothing() ||
!mTexture) {
return;
}
mLastRemoteTextureId = Some(layers::RemoteTextureId::GetNext());
mBridge->SwapChainPresent(mTexture->mId, *mLastRemoteTextureId,
*mRemoteTextureOwnerId);
if (mUseExternalTextureInSwapChain) {
mTexture->ForceDestroy();
mNewTextureRequested = true;
}
}
bool CanvasContext::UpdateWebRenderCanvasData(
mozilla::nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
auto* renderer = aCanvasData->GetCanvasRenderer();
if (renderer && mRemoteTextureOwnerId.isSome() &&
renderer->GetRemoteTextureOwnerIdOfPushCallback() ==
mRemoteTextureOwnerId) {
return true;
}
renderer = aCanvasData->CreateCanvasRenderer();
if (!InitializeCanvasRenderer(aBuilder, renderer)) {
// Clear CanvasRenderer of WebRenderCanvasData
aCanvasData->ClearCanvasRenderer();
return false;
}
return true;
}
bool CanvasContext::InitializeCanvasRenderer(
nsDisplayListBuilder* aBuilder, layers::CanvasRenderer* aRenderer) {
if (mRemoteTextureOwnerId.isNothing()) {
return false;
}
layers::CanvasRendererData data;
data.mContext = this;
data.mSize = mCanvasSize;
data.mIsOpaque = false;
data.mRemoteTextureOwnerIdOfPushCallback = mRemoteTextureOwnerId;
aRenderer->Initialize(data);
aRenderer->SetDirty();
return true;
}
mozilla::UniquePtr<uint8_t[]> CanvasContext::GetImageBuffer(
int32_t* out_format, gfx::IntSize* out_imageSize) {
*out_format = 0;
*out_imageSize = {};
gfxAlphaType any;
RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
if (!snapshot) {
return nullptr;
}
RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
*out_imageSize = dataSurface->GetSize();
if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
gfxUtils::GetImageBufferWithRandomNoise(
dataSurface,
/* aIsAlphaPremultiplied */ true, GetCookieJarSettings(), &*out_format);
}
return gfxUtils::GetImageBuffer(dataSurface, /* aIsAlphaPremultiplied */ true,
&*out_format);
}
NS_IMETHODIMP CanvasContext::GetInputStream(const char* aMimeType,
const nsAString& aEncoderOptions,
nsIInputStream** aStream) {
gfxAlphaType any;
RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
if (!snapshot) {
return NS_ERROR_FAILURE;
}
RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
gfxUtils::GetInputStreamWithRandomNoise(
dataSurface, /* aIsAlphaPremultiplied */ true, aMimeType,
aEncoderOptions, GetCookieJarSettings(), aStream);
}
return gfxUtils::GetInputStream(dataSurface, /* aIsAlphaPremultiplied */ true,
aMimeType, aEncoderOptions, aStream);
}
already_AddRefed<mozilla::gfx::SourceSurface> CanvasContext::GetSurfaceSnapshot(
gfxAlphaType* aOutAlphaType) {
if (aOutAlphaType) {
*aOutAlphaType = gfxAlphaType::Premult;
}
auto* const cm = gfx::CanvasManagerChild::Get();
if (!cm) {
return nullptr;
}
if (!mBridge || !mBridge->IsOpen() || mRemoteTextureOwnerId.isNothing()) {
return nullptr;
}
MOZ_ASSERT(mRemoteTextureOwnerId.isSome());
return cm->GetSnapshot(cm->Id(), mBridge->Id(), mRemoteTextureOwnerId,
mGfxFormat, /* aPremultiply */ false,
/* aYFlip */ false);
}
void CanvasContext::ForceNewFrame() {
if (!mCanvasElement && !mOffscreenCanvas) {
return;
}
// Force a new frame to be built, which will execute the
// `CanvasContextType::WebGPU` switch case in `CreateWebRenderCommands` and
// populate the WR user data.
if (mCanvasElement) {
mCanvasElement->InvalidateCanvas();
} else if (mOffscreenCanvas) {
dom::OffscreenCanvasDisplayData data;
data.mSize = mCanvasSize;
data.mIsOpaque = false;
data.mOwnerId = mRemoteTextureOwnerId;
mOffscreenCanvas->UpdateDisplayData(data);
}
}
} // namespace mozilla::webgpu