gecko-dev/dom/canvas/OffscreenCanvas.h
Andrew Osmond 4d74abb189 Bug 1877429 - Prevent offscreen canvas2d updates from racing with compositing. r=gfx-reviewers,lsalzman
When OffscreenCanvas::CommitFrameToCompositor uses the non-remote
texture canvas path with Skia, it uses ImageBridgeChild for compositing.
When ImageContainer::SetCurrentImages is called, there was an
intermediate state where the relevant textures were not yet marked as
read only for the compositor's consumption, because the event to do so
was dispatched asynchronously to the ImageBridgeChild thread. If the
owning thread of the canvas (main or DOM worker) ran immediately after
CommitFrameToCompositor, then we could run into texture reuse since
nothing marked the texture yet as being used for compositing. This had
the end result of sometimes displaying back buffer textures currently
being used for drawing on the display pipeline.

This patch makes it so that we mark OffscreenCanvas textures as read
only for the compositor before dispatching, and releasing the lock
either when we swap the images in the ImageContainer (winning the race
with ImageBridgeChild), or after the compositor has finished with it
(losing the race, if any, with ImageBridgeChild).

Additionally, to handle better the case where we run out of buffers, we
need to implement ImageBridgeChild::SyncWithCompositor, to be analogous
to how WebRenderBridgeChild::SyncWithCompositor works. We achieve this
by calling from ImageBridgeChild back into the appropriate
WebRenderBridgeChild based on the window ID associated with the canvas,

It also adds a new pref, gfx.offscreencanvas.shared-provider, which
allows one to switch between PersistentBufferProviderShared and Basic.
The latter of which is used if we fallback from using shared buffers if
it takes too long to get the shared buffers back from the compositor.

Differential Revision: https://phabricator.services.mozilla.com/D200991
2024-02-07 20:25:52 +00:00

212 lines
6.9 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/. */
#ifndef MOZILLA_DOM_OFFSCREENCANVAS_H_
#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 "mozilla/UniquePtr.h"
#include "nsCycleCollectionParticipant.h"
struct JSContext;
namespace mozilla {
class CancelableRunnable;
class ErrorResult;
enum class RFPTarget : uint64_t;
namespace gfx {
class SourceSurface;
}
namespace dom {
enum class OffscreenRenderingContextId : uint8_t;
class Blob;
class EncodeCompleteCallback;
class OffscreenCanvasDisplayHelper;
class ImageBitmap;
struct ImageEncodeOptions;
using OwningOffscreenRenderingContext = class
OwningOffscreenCanvasRenderingContext2DOrImageBitmapRenderingContextOrWebGLRenderingContextOrWebGL2RenderingContextOrGPUCanvasContext;
// This is helper class for transferring OffscreenCanvas to worker thread.
// Because OffscreenCanvas is not thread-safe. So we cannot pass Offscreen-
// Canvas to worker thread directly. Thus, we create this helper class and
// store necessary data in it then pass it to worker thread.
struct OffscreenCanvasCloneData final {
OffscreenCanvasCloneData(OffscreenCanvasDisplayHelper* aDisplay,
uint32_t aWidth, uint32_t aHeight,
layers::LayersBackend aCompositorBackend,
layers::TextureType aTextureType, bool aNeutered,
bool aIsWriteOnly, nsIPrincipal* aExpandedReader);
~OffscreenCanvasCloneData();
RefPtr<OffscreenCanvasDisplayHelper> mDisplay;
uint32_t mWidth;
uint32_t mHeight;
layers::LayersBackend mCompositorBackendType;
layers::TextureType mTextureType;
bool mNeutered;
bool mIsWriteOnly;
RefPtr<nsIPrincipal> mExpandedReader;
};
class OffscreenCanvas final : public DOMEventTargetHelper,
public CanvasRenderingContextHelper {
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OffscreenCanvas,
DOMEventTargetHelper)
IMPL_EVENT_HANDLER(contextlost);
IMPL_EVENT_HANDLER(contextrestored);
OffscreenCanvas(nsIGlobalObject* aGlobal, uint32_t aWidth, uint32_t aHeight);
OffscreenCanvas(nsIGlobalObject* aGlobal, uint32_t aWidth, uint32_t aHeight,
layers::LayersBackend aCompositorBackend,
layers::TextureType aTextureType,
already_AddRefed<OffscreenCanvasDisplayHelper> aDisplay);
void Destroy();
nsIGlobalObject* GetParentObject() const { return GetOwnerGlobal(); }
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<OffscreenCanvas> Constructor(
const GlobalObject& aGlobal, uint32_t aWidth, uint32_t aHeight,
ErrorResult& aRv);
void ClearResources();
uint32_t Width() const { return mWidth; }
uint32_t Height() const { return mHeight; }
void SetWidth(uint32_t aWidth, ErrorResult& aRv);
void SetHeight(uint32_t aHeight, ErrorResult& aRv);
void GetContext(JSContext* aCx, const OffscreenRenderingContextId& aContextId,
JS::Handle<JS::Value> aContextOptions,
Nullable<OwningOffscreenRenderingContext>& aResult,
ErrorResult& aRv);
already_AddRefed<ImageBitmap> TransferToImageBitmap(ErrorResult& aRv);
already_AddRefed<Promise> ConvertToBlob(const ImageEncodeOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<Promise> ToBlob(JSContext* aCx, const nsAString& aType,
JS::Handle<JS::Value> aParams,
ErrorResult& aRv);
Maybe<uint64_t> GetWindowID();
nsICanvasRenderingContextInternal* GetContext() const {
return mCurrentContext;
}
CanvasContextType GetContextType() const { return mCurrentContextType; }
already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(
gfxAlphaType* aOutAlphaType = nullptr);
static already_AddRefed<OffscreenCanvas> CreateFromCloneData(
nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData);
// Return true on main-thread, and return gfx.offscreencanvas.enabled
// on worker thread.
static bool PrefEnabledOnWorkerThread(JSContext* aCx, JSObject* aObj);
UniquePtr<OffscreenCanvasCloneData> ToCloneData(JSContext* aCx);
void UpdateDisplayData(const OffscreenCanvasDisplayData& aData);
void CommitFrameToCompositor();
void DequeueCommitToCompositor();
void QueueCommitToCompositor();
virtual bool GetOpaqueAttr() override { return false; }
virtual nsIntSize GetWidthHeight() override {
return nsIntSize(mWidth, mHeight);
}
virtual already_AddRefed<nsICanvasRenderingContextInternal> CreateContext(
CanvasContextType aContextType) override;
void SetNeutered() {
mWidth = 0;
mHeight = 0;
mNeutered = true;
}
bool MayNeuter() const { return !mNeutered && !mCurrentContext; }
void SetSize(const nsIntSize& aSize, ErrorResult& aRv);
nsIPrincipal* GetExpandedReader() const { return mExpandedReader; }
void SetWriteOnly(RefPtr<nsIPrincipal>&& aExpandedReader);
void SetWriteOnly(nsIPrincipal* aExpandedReader = nullptr) {
RefPtr<nsIPrincipal> expandedReader(aExpandedReader);
SetWriteOnly(std::move(expandedReader));
}
bool IsWriteOnly() const { return mIsWriteOnly; }
// Determines if the caller should be able to read the content.
bool CallerCanRead(nsIPrincipal& aPrincipal) const;
layers::LayersBackend GetCompositorBackendType() const {
return mCompositorBackendType;
}
bool ShouldResistFingerprinting(mozilla::RFPTarget aTarget) const;
bool IsTransferredFromElement() const { return !!mDisplay; }
private:
~OffscreenCanvas();
already_AddRefed<EncodeCompleteCallback> CreateEncodeCompleteCallback(
Promise* aPromise);
void CanvasAttrChanged() {
ErrorResult dummy;
UpdateContext(nullptr, JS::NullHandleValue, dummy);
}
bool mNeutered = false;
bool mIsWriteOnly = false;
uint32_t mWidth = 0;
uint32_t mHeight = 0;
layers::LayersBackend mCompositorBackendType =
layers::LayersBackend::LAYERS_NONE;
layers::TextureType mTextureType = layers::TextureType::Unknown;
RefPtr<OffscreenCanvasDisplayHelper> mDisplay;
RefPtr<CancelableRunnable> mPendingCommit;
RefPtr<nsIPrincipal> mExpandedReader;
Maybe<OffscreenCanvasDisplayData> mPendingUpdate;
};
} // namespace dom
} // namespace mozilla
#endif // MOZILLA_DOM_OFFSCREENCANVAS_H_