Bug 1747520 - Ensure that OffscreenCanvas invalidation is in a consistent state. r=gfx-reviewers,kvark

This patch ensures that our invalidation is more consistent and less
frequent. It no longer queues an invalidation for each draw call. It now
combines the parameter update (e.g. width, height, opacity) with an
invalidation to ensure the contents of the canvas are consistent with
the advertised properties. It also ensures that if an explicit commit
occurs, any pending commits are cancelled.

Differential Revision: https://phabricator.services.mozilla.com/D134655
This commit is contained in:
Andrew Osmond
2021-12-24 17:04:39 +00:00
parent f56548352a
commit bc47bc2bda
6 changed files with 115 additions and 91 deletions

View File

@@ -472,10 +472,15 @@ void ClientWebGLContext::UpdateCanvasParameters() {
const auto& options = *mInitialOptions;
const auto& size = DrawingBufferSize();
mOffscreenCanvas->UpdateParameters(
size.x, size.y, options.alpha,
!options.alpha || options.premultipliedAlpha,
/* aIsOriginBottomLeft */ true);
mozilla::dom::OffscreenCanvasDisplayData data;
data.mOriginPos = gl::OriginPos::BottomLeft;
data.mIsOpaque = !options.alpha;
data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
data.mSize = {size.x, size.y};
data.mDoPaintCallbacks = false;
mOffscreenCanvas->UpdateDisplayData(data);
}
layers::LayersBackend ClientWebGLContext::GetCompositorBackendType() const {

View File

@@ -153,6 +153,36 @@ OffscreenCanvas::CreateContext(CanvasContextType aContextType) {
return ret.forget();
}
void OffscreenCanvas::UpdateDisplayData(
const OffscreenCanvasDisplayData& aData) {
if (!mDisplay) {
return;
}
mPendingUpdate = Some(aData);
QueueCommitToCompositor();
}
void OffscreenCanvas::QueueCommitToCompositor() {
if (!mDisplay || !mCurrentContext || mPendingCommit) {
// If we already have a commit pending, or we have no bound display/context,
// just bail out.
return;
}
mPendingCommit = NS_NewCancelableRunnableFunction(
"OffscreenCanvas::QueueCommitToCompositor",
[self = RefPtr{this}] { self->DequeueCommitToCompositor(); });
NS_DispatchToCurrentThread(mPendingCommit);
}
void OffscreenCanvas::DequeueCommitToCompositor() {
MOZ_ASSERT(mPendingCommit);
mPendingCommit = nullptr;
Maybe<OffscreenCanvasDisplayData> update = std::move(mPendingUpdate);
mDisplay->CommitFrameToCompositor(mCurrentContext, mTextureType, update);
}
void OffscreenCanvas::CommitFrameToCompositor() {
if (!mDisplay || !mCurrentContext) {
// This offscreen canvas doesn't associate to any HTML canvas element.
@@ -160,24 +190,14 @@ void OffscreenCanvas::CommitFrameToCompositor() {
return;
}
mDisplay->CommitFrameToCompositor(mCurrentContext, mTextureType);
if (mPendingCommit) {
// We got an explicit commit while waiting for an implicit.
mPendingCommit->Cancel();
mPendingCommit = nullptr;
}
void OffscreenCanvas::UpdateParameters(uint32_t aWidth, uint32_t aHeight,
bool aHasAlpha, bool aIsPremultiplied,
bool aIsOriginBottomLeft) {
if (!mDisplay) {
return;
}
mDisplay->UpdateParameters(aWidth, aHeight, aHasAlpha, aIsPremultiplied,
aIsOriginBottomLeft);
}
void OffscreenCanvas::QueueCommitToCompositor() {
NS_DispatchToCurrentThread(NS_NewCancelableRunnableFunction(
"OffscreenCanvas::QueueCommitToCompositor",
[self = RefPtr{this}] { self->CommitFrameToCompositor(); }));
Maybe<OffscreenCanvasDisplayData> update = std::move(mPendingUpdate);
mDisplay->CommitFrameToCompositor(mCurrentContext, mTextureType, update);
}
OffscreenCanvasCloneData* OffscreenCanvas::ToCloneData() {

View File

@@ -8,17 +8,19 @@
#define MOZILLA_DOM_OFFSCREENCANVAS_H_
#include "gfxTypes.h"
#include "mozilla/dom/CanvasRenderingContextHelper.h"
#include "mozilla/dom/ImageEncoder.h"
#include "mozilla/dom/OffscreenCanvasDisplayHelper.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "CanvasRenderingContextHelper.h"
#include "nsCycleCollectionParticipant.h"
struct JSContext;
namespace mozilla {
class CancelableRunnable;
class ErrorResult;
namespace gfx {
@@ -148,10 +150,11 @@ class OffscreenCanvas final : public DOMEventTargetHelper,
OffscreenCanvasCloneData* ToCloneData();
void UpdateParameters(uint32_t aWidth, uint32_t aHeight, bool aHasAlpha,
bool aIsPremultiplied, bool aIsOriginBottomLeft);
void UpdateDisplayData(const OffscreenCanvasDisplayData& aData);
void CommitFrameToCompositor();
void DequeueCommitToCompositor();
void QueueCommitToCompositor();
virtual bool GetOpaqueAttr() override { return false; }
@@ -176,8 +179,6 @@ class OffscreenCanvas final : public DOMEventTargetHelper,
bool ShouldResistFingerprinting() const;
void QueueCommitToCompositor();
private:
~OffscreenCanvas();
@@ -201,6 +202,8 @@ class OffscreenCanvas final : public DOMEventTargetHelper,
layers::TextureType mTextureType;
RefPtr<OffscreenCanvasDisplayHelper> mDisplay;
RefPtr<CancelableRunnable> mPendingCommit;
Maybe<OffscreenCanvasDisplayData> mPendingUpdate;
};
} // namespace dom

View File

@@ -10,6 +10,7 @@
#include "mozilla/layers/TextureClientSharedSurface.h"
#include "mozilla/layers/TextureWrapperImage.h"
#include "mozilla/SVGObserverUtils.h"
#include "nsICanvasRenderingContextInternal.h"
namespace mozilla::dom {
@@ -17,9 +18,10 @@ OffscreenCanvasDisplayHelper::OffscreenCanvasDisplayHelper(
HTMLCanvasElement* aCanvasElement, uint32_t aWidth, uint32_t aHeight)
: mMutex("mozilla::dom::OffscreenCanvasDisplayHelper"),
mCanvasElement(aCanvasElement),
mWidth(aWidth),
mHeight(aHeight),
mImageProducerID(layers::ImageContainer::AllocateProducerID()) {}
mImageProducerID(layers::ImageContainer::AllocateProducerID()) {
mData.mSize.width = aWidth;
mData.mSize.height = aHeight;
}
OffscreenCanvasDisplayHelper::~OffscreenCanvasDisplayHelper() = default;
@@ -56,30 +58,10 @@ void OffscreenCanvasDisplayHelper::UpdateContext(CanvasContextType aType,
MaybeQueueInvalidateElement();
}
void OffscreenCanvasDisplayHelper::UpdateParameters(uint32_t aWidth,
uint32_t aHeight,
bool aHasAlpha,
bool aIsPremultiplied,
bool aIsOriginBottomLeft) {
MutexAutoLock lock(mMutex);
if (!mCanvasElement) {
// Our weak reference to the canvas element has been cleared, so we cannot
// present directly anymore.
return;
}
mWidth = aWidth;
mHeight = aHeight;
mHasAlpha = aHasAlpha;
mIsPremultiplied = aIsPremultiplied;
mIsOriginBottomLeft = aIsOriginBottomLeft;
MaybeQueueInvalidateElement();
}
bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
nsICanvasRenderingContextInternal* aContext,
layers::TextureType aTextureType) {
layers::TextureType aTextureType,
const Maybe<OffscreenCanvasDisplayData>& aData) {
MutexAutoLock lock(mMutex);
gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
@@ -91,15 +73,27 @@ bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
return false;
}
if (!mHasAlpha) {
if (aData) {
mData = aData.ref();
MaybeQueueInvalidateElement();
}
if (mData.mIsOpaque) {
flags |= layers::TextureFlags::IS_OPAQUE;
format = gfx::SurfaceFormat::B8G8R8X8;
} else if (!mIsPremultiplied) {
} else if (!mData.mIsAlphaPremult) {
flags |= layers::TextureFlags::NON_PREMULTIPLIED;
}
if (mIsOriginBottomLeft) {
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();
@@ -107,6 +101,10 @@ bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
return false;
}
if (mData.mDoPaintCallbacks) {
aContext->OnBeforePaintTransaction();
}
RefPtr<layers::Image> image;
RefPtr<gfx::SourceSurface> surface;
@@ -115,10 +113,10 @@ bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
if (desc) {
RefPtr<layers::TextureClient> texture =
layers::SharedSurfaceTextureData::CreateTextureClient(
*desc, format, gfx::IntSize(mWidth, mHeight), flags, imageBridge);
*desc, format, mData.mSize, flags, imageBridge);
if (texture) {
image = new layers::TextureWrapperImage(
texture, gfx::IntRect(0, 0, mWidth, mHeight));
texture, gfx::IntRect(gfx::IntPoint(0, 0), mData.mSize));
}
} else {
surface = aContext->GetFrontBufferSnapshot(/* requireAlphaPremult */ true);
@@ -127,6 +125,10 @@ bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
}
}
if (mData.mDoPaintCallbacks) {
aContext->OnDidPaintTransaction();
}
if (image) {
AutoTArray<layers::ImageContainer::NonOwningImage, 1> imageList;
imageList.AppendElement(layers::ImageContainer::NonOwningImage(
@@ -142,12 +144,13 @@ bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
}
void OffscreenCanvasDisplayHelper::MaybeQueueInvalidateElement() {
mMutex.AssertCurrentThreadOwns();
if (!mPendingInvalidate) {
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
"OffscreenCanvasDisplayHelper::InvalidateElement",
[self = RefPtr{this}]() { self->InvalidateElement(); });
NS_DispatchToMainThread(runnable.forget());
mPendingInvalidate = true;
NS_DispatchToMainThread(NS_NewRunnableFunction(
"OffscreenCanvasDisplayHelper::InvalidateElement",
[self = RefPtr{this}] { self->InvalidateElement(); }));
}
}
@@ -155,32 +158,25 @@ void OffscreenCanvasDisplayHelper::InvalidateElement() {
MOZ_ASSERT(NS_IsMainThread());
HTMLCanvasElement* canvasElement;
uint32_t width, height;
gfx::IntSize size;
{
MutexAutoLock lock(mMutex);
MOZ_ASSERT(mPendingInvalidate);
mPendingInvalidate = false;
canvasElement = mCanvasElement;
width = mWidth;
height = mHeight;
size = mData.mSize;
}
if (canvasElement) {
SVGObserverUtils::InvalidateDirectRenderingObservers(canvasElement);
canvasElement->InvalidateCanvasPlaceholder(width, height);
canvasElement->InvalidateCanvasPlaceholder(size.width, size.height);
canvasElement->InvalidateCanvasContent(nullptr);
}
}
Maybe<layers::SurfaceDescriptor> OffscreenCanvasDisplayHelper::GetFrontBuffer(
WebGLFramebufferJS*, const bool webvr) {
MutexAutoLock lock(mMutex);
return mFrontBufferDesc;
}
already_AddRefed<gfx::SourceSurface>
OffscreenCanvasDisplayHelper::GetSurfaceSnapshot(gfxAlphaType* out_alphaType) {
OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
MOZ_ASSERT(NS_IsMainThread());
Maybe<layers::SurfaceDescriptor> desc;
@@ -196,7 +192,7 @@ OffscreenCanvasDisplayHelper::GetSurfaceSnapshot(gfxAlphaType* out_alphaType) {
return surface.forget();
}
hasAlpha = mHasAlpha;
hasAlpha = !mData.mIsOpaque;
managerId = mContextManagerId;
childId = mContextChildId;
}

View File

@@ -7,18 +7,29 @@
#ifndef MOZILLA_DOM_OFFSCREENCANVASDISPLAYHELPER_H_
#define MOZILLA_DOM_OFFSCREENCANVASDISPLAYHELPER_H_
#include "CanvasRenderingContextHelper.h"
#include "ImageContainer.h"
#include "GLContextTypes.h"
#include "mozilla/dom/CanvasRenderingContextHelper.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/layers/LayersSurfaces.h"
#include "mozilla/Maybe.h"
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "nsICanvasRenderingContextInternal.h"
#include "nsISupportsImpl.h"
#include "nsThreadUtils.h"
namespace mozilla::dom {
class HTMLCanvasElement;
struct OffscreenCanvasDisplayData final {
mozilla::gfx::IntSize mSize = {0, 0};
bool mDoPaintCallbacks = false;
bool mIsOpaque = true;
bool mIsAlphaPremult = true;
mozilla::gl::OriginPos mOriginPos = gl::OriginPos::TopLeft;
};
class OffscreenCanvasDisplayHelper final {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OffscreenCanvasDisplayHelper)
@@ -32,20 +43,13 @@ class OffscreenCanvasDisplayHelper final {
void UpdateContext(CanvasContextType aType, int32_t aChildId);
void UpdateParameters(uint32_t aWidth, uint32_t aHeight, bool aHasAlpha,
bool aIsPremultiplied, bool aIsOriginBottomLeft);
bool CommitFrameToCompositor(nsICanvasRenderingContextInternal* aContext,
layers::TextureType aTextureType);
layers::TextureType aTextureType,
const Maybe<OffscreenCanvasDisplayData>& aData);
void Destroy();
mozilla::Maybe<mozilla::layers::SurfaceDescriptor> GetFrontBuffer(
mozilla::WebGLFramebufferJS*, const bool webvr = false);
already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(
gfxAlphaType* out_alphaType = nullptr);
already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot();
already_AddRefed<mozilla::layers::Image> GetAsImage();
private:
@@ -59,17 +63,13 @@ class OffscreenCanvasDisplayHelper final {
RefPtr<gfx::SourceSurface> mFrontBufferSurface;
Maybe<layers::SurfaceDescriptor> mFrontBufferDesc;
OffscreenCanvasDisplayData mData;
CanvasContextType mType = CanvasContextType::NoContext;
uint32_t mWidth;
uint32_t mHeight;
uint32_t mContextManagerId = 0;
int32_t mContextChildId = 0;
mozilla::layers::ImageContainer::ProducerID mImageProducerID;
mozilla::layers::ImageContainer::FrameID mLastFrameID = 0;
bool mPendingInvalidate = false;
bool mHasAlpha = false;
bool mIsPremultiplied = false;
bool mIsOriginBottomLeft = false;
};
} // namespace mozilla::dom

View File

@@ -1253,7 +1253,7 @@ already_AddRefed<SourceSurface> HTMLCanvasElement::GetSurfaceSnapshot(
if (mCurrentContext) {
return mCurrentContext->GetSurfaceSnapshot(aOutAlphaType);
} else if (mOffscreenDisplay) {
return mOffscreenDisplay->GetSurfaceSnapshot(aOutAlphaType);
return mOffscreenDisplay->GetSurfaceSnapshot();
}
return nullptr;
}