forked from mirrors/gecko-dev
Backed out changeset 05b607c3bbe6 (bug 1795296) Backed out changeset 7704291111b4 (bug 1855742) Backed out changeset db60743295fb (bug 1855742) Backed out changeset 8ed51e7d1c98 (bug 1855742) Backed out changeset 40cba2c51b1c (bug 1855742) Backed out changeset d4bfe15c07ec (bug 1855742) Backed out changeset 2061271c53e8 (bug 1855742) Backed out changeset 5b2a89a61f7f (bug 1855742) Backed out changeset 2028c7018977 (bug 1855742) Backed out changeset 13e806495fb8 (bug 1855742) Backed out changeset 4bfa0d4913a2 (bug 1855742) Backed out changeset 711b3f47e380 (bug 1855742)
507 lines
16 KiB
C++
507 lines
16 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* 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 "OffscreenCanvasDisplayHelper.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/dom/WorkerRef.h"
|
|
#include "mozilla/dom/WorkerRunnable.h"
|
|
#include "mozilla/gfx/2D.h"
|
|
#include "mozilla/gfx/CanvasManagerChild.h"
|
|
#include "mozilla/gfx/DataSurfaceHelpers.h"
|
|
#include "mozilla/gfx/Swizzle.h"
|
|
#include "mozilla/layers/ImageBridgeChild.h"
|
|
#include "mozilla/layers/TextureClientSharedSurface.h"
|
|
#include "mozilla/layers/TextureWrapperImage.h"
|
|
#include "mozilla/SVGObserverUtils.h"
|
|
#include "nsICanvasRenderingContextInternal.h"
|
|
#include "nsRFPService.h"
|
|
|
|
namespace mozilla::dom {
|
|
|
|
OffscreenCanvasDisplayHelper::OffscreenCanvasDisplayHelper(
|
|
HTMLCanvasElement* aCanvasElement, uint32_t aWidth, uint32_t aHeight)
|
|
: mMutex("mozilla::dom::OffscreenCanvasDisplayHelper"),
|
|
mCanvasElement(aCanvasElement),
|
|
mImageProducerID(layers::ImageContainer::AllocateProducerID()) {
|
|
mData.mSize.width = aWidth;
|
|
mData.mSize.height = aHeight;
|
|
}
|
|
|
|
OffscreenCanvasDisplayHelper::~OffscreenCanvasDisplayHelper() = default;
|
|
|
|
void OffscreenCanvasDisplayHelper::DestroyElement() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
mCanvasElement = nullptr;
|
|
}
|
|
|
|
void OffscreenCanvasDisplayHelper::DestroyCanvas() {
|
|
MutexAutoLock lock(mMutex);
|
|
mOffscreenCanvas = nullptr;
|
|
mWorkerRef = nullptr;
|
|
}
|
|
|
|
CanvasContextType OffscreenCanvasDisplayHelper::GetContextType() const {
|
|
MutexAutoLock lock(mMutex);
|
|
return mType;
|
|
}
|
|
|
|
RefPtr<layers::ImageContainer> OffscreenCanvasDisplayHelper::GetImageContainer()
|
|
const {
|
|
MutexAutoLock lock(mMutex);
|
|
return mImageContainer;
|
|
}
|
|
|
|
void OffscreenCanvasDisplayHelper::UpdateContext(
|
|
OffscreenCanvas* aOffscreenCanvas, RefPtr<ThreadSafeWorkerRef>&& aWorkerRef,
|
|
CanvasContextType aType, const Maybe<int32_t>& aChildId) {
|
|
RefPtr<layers::ImageContainer> imageContainer =
|
|
MakeRefPtr<layers::ImageContainer>(layers::ImageContainer::ASYNCHRONOUS);
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
mOffscreenCanvas = aOffscreenCanvas;
|
|
mWorkerRef = std::move(aWorkerRef);
|
|
mType = aType;
|
|
mContextChildId = aChildId;
|
|
mImageContainer = std::move(imageContainer);
|
|
|
|
if (aChildId) {
|
|
mContextManagerId = Some(gfx::CanvasManagerChild::Get()->Id());
|
|
} else {
|
|
mContextManagerId.reset();
|
|
}
|
|
|
|
MaybeQueueInvalidateElement();
|
|
}
|
|
|
|
void OffscreenCanvasDisplayHelper::FlushForDisplay() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
// Without an OffscreenCanvas object bound to us, we either have not drawn
|
|
// using the canvas at all, or we have already destroyed it.
|
|
if (!mOffscreenCanvas) {
|
|
return;
|
|
}
|
|
|
|
// We assign/destroy the worker ref at the same time as the OffscreenCanvas
|
|
// ref, so we can only have the canvas without a worker ref if it exists on
|
|
// the main thread.
|
|
if (!mWorkerRef) {
|
|
// We queue to ensure that we have the same asynchronous update behaviour
|
|
// for a main thread and a worker based OffscreenCanvas.
|
|
mOffscreenCanvas->QueueCommitToCompositor();
|
|
return;
|
|
}
|
|
|
|
class FlushWorkerRunnable final : public WorkerRunnable {
|
|
public:
|
|
FlushWorkerRunnable(WorkerPrivate* aWorkerPrivate,
|
|
OffscreenCanvasDisplayHelper* aDisplayHelper)
|
|
: WorkerRunnable(aWorkerPrivate), mDisplayHelper(aDisplayHelper) {}
|
|
|
|
bool WorkerRun(JSContext*, WorkerPrivate*) override {
|
|
// The OffscreenCanvas can only be freed on the worker thread, so we
|
|
// cannot be racing with an OffscreenCanvas::DestroyCanvas call and its
|
|
// destructor. We just need to make sure we don't call into
|
|
// OffscreenCanvas while holding the lock since it calls back into the
|
|
// OffscreenCanvasDisplayHelper.
|
|
RefPtr<OffscreenCanvas> canvas;
|
|
{
|
|
MutexAutoLock lock(mDisplayHelper->mMutex);
|
|
canvas = mDisplayHelper->mOffscreenCanvas;
|
|
}
|
|
|
|
if (canvas) {
|
|
canvas->CommitFrameToCompositor();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
RefPtr<OffscreenCanvasDisplayHelper> mDisplayHelper;
|
|
};
|
|
|
|
// Otherwise we are calling from the main thread during painting to a canvas
|
|
// on a worker thread.
|
|
auto task = MakeRefPtr<FlushWorkerRunnable>(mWorkerRef->Private(), this);
|
|
task->Dispatch();
|
|
}
|
|
|
|
bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
|
|
nsICanvasRenderingContextInternal* aContext,
|
|
layers::TextureType aTextureType,
|
|
const Maybe<OffscreenCanvasDisplayData>& aData) {
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
|
|
layers::TextureFlags flags = layers::TextureFlags::IMMUTABLE;
|
|
|
|
if (!mCanvasElement) {
|
|
// Our weak reference to the canvas element has been cleared, so we cannot
|
|
// present directly anymore.
|
|
return false;
|
|
}
|
|
|
|
if (aData) {
|
|
mData = aData.ref();
|
|
MaybeQueueInvalidateElement();
|
|
}
|
|
|
|
if (mData.mOwnerId.isSome()) {
|
|
// No need to update the ImageContainer as the presentation itself is
|
|
// handled in the compositor process.
|
|
return true;
|
|
}
|
|
|
|
if (!mImageContainer) {
|
|
return false;
|
|
}
|
|
|
|
if (mData.mIsOpaque) {
|
|
flags |= layers::TextureFlags::IS_OPAQUE;
|
|
format = gfx::SurfaceFormat::B8G8R8X8;
|
|
} else if (!mData.mIsAlphaPremult) {
|
|
flags |= layers::TextureFlags::NON_PREMULTIPLIED;
|
|
}
|
|
|
|
switch (mData.mOriginPos) {
|
|
case gl::OriginPos::BottomLeft:
|
|
flags |= layers::TextureFlags::ORIGIN_BOTTOM_LEFT;
|
|
break;
|
|
case gl::OriginPos::TopLeft:
|
|
break;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unhandled origin position!");
|
|
break;
|
|
}
|
|
|
|
auto imageBridge = layers::ImageBridgeChild::GetSingleton();
|
|
if (!imageBridge) {
|
|
return false;
|
|
}
|
|
|
|
bool paintCallbacks = mData.mDoPaintCallbacks;
|
|
RefPtr<layers::Image> image;
|
|
RefPtr<gfx::SourceSurface> surface;
|
|
Maybe<layers::SurfaceDescriptor> desc;
|
|
|
|
{
|
|
MutexAutoUnlock unlock(mMutex);
|
|
if (paintCallbacks) {
|
|
aContext->OnBeforePaintTransaction();
|
|
}
|
|
|
|
desc = aContext->PresentFrontBuffer(nullptr, aTextureType);
|
|
if (!desc) {
|
|
surface =
|
|
aContext->GetFrontBufferSnapshot(/* requireAlphaPremult */ false);
|
|
if (surface && surface->GetType() == gfx::SurfaceType::WEBGL) {
|
|
// Ensure we can map in the surface. If we get a SourceSurfaceWebgl
|
|
// surface, then it may not be backed by raw pixels yet. We need to map
|
|
// it on the owning thread rather than the ImageBridge thread.
|
|
gfx::DataSourceSurface::ScopedMap map(
|
|
static_cast<gfx::DataSourceSurface*>(surface.get()),
|
|
gfx::DataSourceSurface::READ);
|
|
if (!map.IsMapped()) {
|
|
surface = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (paintCallbacks) {
|
|
aContext->OnDidPaintTransaction();
|
|
}
|
|
}
|
|
|
|
if (desc) {
|
|
if (desc->type() ==
|
|
layers::SurfaceDescriptor::TSurfaceDescriptorRemoteTexture) {
|
|
const auto& textureDesc = desc->get_SurfaceDescriptorRemoteTexture();
|
|
imageBridge->UpdateCompositable(mImageContainer, textureDesc.textureId(),
|
|
textureDesc.ownerId(), mData.mSize,
|
|
flags);
|
|
} else {
|
|
RefPtr<layers::TextureClient> texture =
|
|
layers::SharedSurfaceTextureData::CreateTextureClient(
|
|
*desc, format, mData.mSize, flags, imageBridge);
|
|
if (texture) {
|
|
image = new layers::TextureWrapperImage(
|
|
texture, gfx::IntRect(gfx::IntPoint(0, 0), mData.mSize));
|
|
}
|
|
}
|
|
} else if (surface) {
|
|
auto surfaceImage = MakeRefPtr<layers::SourceSurfaceImage>(surface);
|
|
surfaceImage->SetTextureFlags(flags);
|
|
image = surfaceImage;
|
|
}
|
|
|
|
if (image) {
|
|
AutoTArray<layers::ImageContainer::NonOwningImage, 1> imageList;
|
|
imageList.AppendElement(layers::ImageContainer::NonOwningImage(
|
|
image, TimeStamp(), mLastFrameID++, mImageProducerID));
|
|
mImageContainer->SetCurrentImages(imageList);
|
|
} else if (!desc ||
|
|
desc->type() !=
|
|
layers::SurfaceDescriptor::TSurfaceDescriptorRemoteTexture) {
|
|
mImageContainer->ClearAllImages();
|
|
}
|
|
|
|
// We save any current surface because we might need it in GetSnapshot. If we
|
|
// are on a worker thread and not WebGL, then this will be the only way we can
|
|
// access the pixel data on the main thread.
|
|
mFrontBufferSurface = surface;
|
|
return true;
|
|
}
|
|
|
|
void OffscreenCanvasDisplayHelper::MaybeQueueInvalidateElement() {
|
|
if (!mPendingInvalidate) {
|
|
mPendingInvalidate = true;
|
|
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
|
"OffscreenCanvasDisplayHelper::InvalidateElement",
|
|
[self = RefPtr{this}] { self->InvalidateElement(); }));
|
|
}
|
|
}
|
|
|
|
void OffscreenCanvasDisplayHelper::InvalidateElement() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
HTMLCanvasElement* canvasElement;
|
|
gfx::IntSize size;
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mPendingInvalidate);
|
|
mPendingInvalidate = false;
|
|
canvasElement = mCanvasElement;
|
|
size = mData.mSize;
|
|
}
|
|
|
|
if (canvasElement) {
|
|
SVGObserverUtils::InvalidateDirectRenderingObservers(canvasElement);
|
|
canvasElement->InvalidateCanvasPlaceholder(size.width, size.height);
|
|
canvasElement->InvalidateCanvasContent(nullptr);
|
|
}
|
|
}
|
|
|
|
bool OffscreenCanvasDisplayHelper::TransformSurface(
|
|
const gfx::DataSourceSurface::ScopedMap& aSrcMap,
|
|
const gfx::DataSourceSurface::ScopedMap& aDstMap,
|
|
gfx::SurfaceFormat aFormat, const gfx::IntSize& aSize, bool aNeedsPremult,
|
|
gl::OriginPos aOriginPos) const {
|
|
if (!aSrcMap.IsMapped() || !aDstMap.IsMapped()) {
|
|
return false;
|
|
}
|
|
|
|
switch (aOriginPos) {
|
|
case gl::OriginPos::BottomLeft:
|
|
if (aNeedsPremult) {
|
|
return gfx::PremultiplyYFlipData(aSrcMap.GetData(), aSrcMap.GetStride(),
|
|
aFormat, aDstMap.GetData(),
|
|
aDstMap.GetStride(), aFormat, aSize);
|
|
}
|
|
return gfx::SwizzleYFlipData(aSrcMap.GetData(), aSrcMap.GetStride(),
|
|
aFormat, aDstMap.GetData(),
|
|
aDstMap.GetStride(), aFormat, aSize);
|
|
case gl::OriginPos::TopLeft:
|
|
if (aNeedsPremult) {
|
|
return gfx::PremultiplyData(aSrcMap.GetData(), aSrcMap.GetStride(),
|
|
aFormat, aDstMap.GetData(),
|
|
aDstMap.GetStride(), aFormat, aSize);
|
|
}
|
|
return gfx::SwizzleData(aSrcMap.GetData(), aSrcMap.GetStride(), aFormat,
|
|
aDstMap.GetData(), aDstMap.GetStride(), aFormat,
|
|
aSize);
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unhandled origin position!");
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
already_AddRefed<gfx::SourceSurface>
|
|
OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
Maybe<layers::SurfaceDescriptor> desc;
|
|
|
|
bool hasAlpha;
|
|
bool isAlphaPremult;
|
|
gl::OriginPos originPos;
|
|
Maybe<uint32_t> managerId;
|
|
Maybe<int32_t> childId;
|
|
HTMLCanvasElement* canvasElement;
|
|
RefPtr<gfx::SourceSurface> surface;
|
|
Maybe<layers::RemoteTextureOwnerId> ownerId;
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
hasAlpha = !mData.mIsOpaque;
|
|
isAlphaPremult = mData.mIsAlphaPremult;
|
|
originPos = mData.mOriginPos;
|
|
ownerId = mData.mOwnerId;
|
|
managerId = mContextManagerId;
|
|
childId = mContextChildId;
|
|
canvasElement = mCanvasElement;
|
|
surface = mFrontBufferSurface;
|
|
}
|
|
|
|
if (surface) {
|
|
// We already have a copy of the front buffer in our process.
|
|
|
|
if (originPos == gl::OriginPos::TopLeft && (!hasAlpha || isAlphaPremult)) {
|
|
// If we don't need to y-flip, and it is either opaque or premultiplied,
|
|
// we can just return the same surface.
|
|
return surface.forget();
|
|
}
|
|
|
|
// Otherwise we need to copy and apply the necessary transformations.
|
|
RefPtr<gfx::DataSourceSurface> srcSurface = surface->GetDataSurface();
|
|
if (!srcSurface) {
|
|
return nullptr;
|
|
}
|
|
|
|
const auto size = srcSurface->GetSize();
|
|
const auto format = srcSurface->GetFormat();
|
|
|
|
RefPtr<gfx::DataSourceSurface> dstSurface =
|
|
gfx::Factory::CreateDataSourceSurface(size, format, /* aZero */ false);
|
|
if (!dstSurface) {
|
|
return nullptr;
|
|
}
|
|
|
|
gfx::DataSourceSurface::ScopedMap srcMap(srcSurface,
|
|
gfx::DataSourceSurface::READ);
|
|
gfx::DataSourceSurface::ScopedMap dstMap(dstSurface,
|
|
gfx::DataSourceSurface::WRITE);
|
|
if (!TransformSurface(srcMap, dstMap, format, size,
|
|
hasAlpha && !isAlphaPremult, originPos)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return dstSurface.forget();
|
|
}
|
|
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
// On Android, we cannot both display a GL context and read back the pixels.
|
|
if (canvasElement) {
|
|
return nullptr;
|
|
}
|
|
#endif
|
|
|
|
if (managerId && childId) {
|
|
// We don't have a usable surface, and the context lives in the compositor
|
|
// process.
|
|
return gfx::CanvasManagerChild::Get()->GetSnapshot(
|
|
managerId.value(), childId.value(), ownerId,
|
|
hasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8,
|
|
hasAlpha && !isAlphaPremult, originPos == gl::OriginPos::BottomLeft);
|
|
}
|
|
|
|
// If we don't have any protocol IDs, or an existing surface, it is possible
|
|
// it is a main thread OffscreenCanvas instance. If so, then the element's
|
|
// OffscreenCanvas is not neutered and has access to the context. We can use
|
|
// that to get the snapshot directly.
|
|
if (!canvasElement) {
|
|
return nullptr;
|
|
}
|
|
|
|
const auto* offscreenCanvas = canvasElement->GetOffscreenCanvas();
|
|
nsICanvasRenderingContextInternal* context = offscreenCanvas->GetContext();
|
|
if (!context) {
|
|
return nullptr;
|
|
}
|
|
|
|
surface = context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false);
|
|
if (!surface) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (originPos == gl::OriginPos::TopLeft && (!hasAlpha || isAlphaPremult)) {
|
|
// If we don't need to y-flip, and it is either opaque or premultiplied,
|
|
// we can just return the same surface.
|
|
return surface.forget();
|
|
}
|
|
|
|
// Otherwise we need to apply the necessary transformations in place.
|
|
RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
|
|
if (!dataSurface) {
|
|
return nullptr;
|
|
}
|
|
|
|
gfx::DataSourceSurface::ScopedMap map(dataSurface,
|
|
gfx::DataSourceSurface::READ_WRITE);
|
|
if (!TransformSurface(map, map, dataSurface->GetFormat(),
|
|
dataSurface->GetSize(), hasAlpha && !isAlphaPremult,
|
|
originPos)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return surface.forget();
|
|
}
|
|
|
|
already_AddRefed<layers::Image> OffscreenCanvasDisplayHelper::GetAsImage() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<gfx::SourceSurface> surface = GetSurfaceSnapshot();
|
|
if (!surface) {
|
|
return nullptr;
|
|
}
|
|
return MakeAndAddRef<layers::SourceSurfaceImage>(surface);
|
|
}
|
|
|
|
UniquePtr<uint8_t[]> OffscreenCanvasDisplayHelper::GetImageBuffer(
|
|
int32_t* aOutFormat, gfx::IntSize* aOutImageSize) {
|
|
RefPtr<gfx::SourceSurface> surface = GetSurfaceSnapshot();
|
|
if (!surface) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
|
|
if (!dataSurface) {
|
|
return nullptr;
|
|
}
|
|
|
|
*aOutFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
|
|
*aOutImageSize = dataSurface->GetSize();
|
|
|
|
UniquePtr<uint8_t[]> imageBuffer = gfx::SurfaceToPackedBGRA(dataSurface);
|
|
if (!imageBuffer) {
|
|
return nullptr;
|
|
}
|
|
|
|
bool resistFingerprinting;
|
|
nsICookieJarSettings* cookieJarSettings = nullptr;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
if (mCanvasElement) {
|
|
Document* doc = mCanvasElement->OwnerDoc();
|
|
resistFingerprinting =
|
|
doc->ShouldResistFingerprinting(RFPTarget::CanvasRandomization);
|
|
if (resistFingerprinting) {
|
|
cookieJarSettings = doc->CookieJarSettings();
|
|
}
|
|
} else {
|
|
resistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
|
|
"Fallback", RFPTarget::CanvasRandomization);
|
|
}
|
|
}
|
|
|
|
if (resistFingerprinting) {
|
|
nsRFPService::RandomizePixels(
|
|
cookieJarSettings, imageBuffer.get(), dataSurface->GetSize().width,
|
|
dataSurface->GetSize().height,
|
|
dataSurface->GetSize().width * dataSurface->GetSize().height * 4,
|
|
gfx::SurfaceFormat::A8R8G8B8_UINT32);
|
|
}
|
|
return imageBuffer;
|
|
}
|
|
|
|
} // namespace mozilla::dom
|