fune/dom/canvas/DrawTargetWebgl.h
Lee Salzman a57e6f5dfb Bug 1789849 - Use a separate DrawTargetSkia for rendering clip masks. r=aosmond
Previously we were reusing the framebuffer's Skia DT to render the clip mask.
This was the path of least resistance since SkCanvas does not allow exporting
clip information, and there is no way to reset the bitmap storage inside an
SkCanvas temporarily.

However, this can cause a feedback cycle of unnecessary WaitForShmem operations,
since we need to wait before we can generate the clip mask into the Skia target,
and then anything else after it needs to wait for the clip mask to finish uploading
before the Skia DT can be used again.

To alleviate this, we just allocate a new DrawTargetSkia to render the clip mask
into. We carefully clip the size of the DT so that in the common case we avoid
having to upload a surface the size of the entire framebuffer. Further, since
this is a completely different DT, we can now use an A8 format (1/4 the memory
overhead) instead of a BGRA8 format for the clip mask, which gives a further
memory usage gain.

A further complication is that we need to log the current clip stack state so
that we can replay it onto the new DrawTargetSkia. This avoids having to add
a mechanism to SkCanvas to export clip information.

Differential Revision: https://phabricator.services.mozilla.com/D157050
2022-09-12 15:26:03 +00:00

526 lines
21 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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_GFX_DRAWTARGETWEBGL_H
#define _MOZILLA_GFX_DRAWTARGETWEBGL_H
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/PathSkia.h"
#include "mozilla/LinkedList.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/ThreadLocal.h"
#include "mozilla/ipc/Shmem.h"
#include <vector>
namespace mozilla {
class ClientWebGLContext;
class WebGLBufferJS;
class WebGLFramebufferJS;
class WebGLProgramJS;
class WebGLRenderbufferJS;
class WebGLTextureJS;
class WebGLUniformLocationJS;
class WebGLVertexArrayJS;
namespace layers {
class SurfaceDescriptor;
}
namespace gfx {
class DataSourceSurface;
class DrawTargetSkia;
class DrawTargetWebgl;
class PathSkia;
class SourceSurfaceSkia;
class SourceSurfaceWebgl;
class TextureHandle;
class SharedTexture;
class SharedTextureHandle;
class StandaloneTexture;
class GlyphCache;
class PathCache;
// DrawTargetWebgl implements a subset of the DrawTarget API suitable for use
// by CanvasRenderingContext2D. It maps these to a client WebGL context so that
// they can be accelerated where possible by WebGL. It manages both routing to
// appropriate shaders and texture allocation/caching for surfaces. For commands
// that are not feasible to accelerate with WebGL, it mirrors state to a backup
// DrawTargetSkia that can be used as a fallback software renderer. Multiple
// instances of DrawTargetWebgl within a process will actually share a single
// WebGL context so that data can be more easily interchanged between them and
// also to enable more reasonable limiting of resource usage.
class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
friend class SharedTextureHandle;
friend class StandaloneTexture;
friend class TextureHandle;
friend class SourceSurfaceWebgl;
friend class AutoSaveContext;
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetWebgl, override)
private:
IntSize mSize;
RefPtr<WebGLFramebufferJS> mFramebuffer;
RefPtr<WebGLTextureJS> mTex;
RefPtr<WebGLTextureJS> mClipMask;
IntRect mClipBounds;
RefPtr<DrawTargetSkia> mSkia;
// The Shmem backing the Skia DT, if applicable.
mozilla::ipc::Shmem mShmem;
// The currently cached snapshot of the WebGL context
RefPtr<DataSourceSurface> mSnapshot;
// Whether or not the Skia target has valid contents and is being drawn to
bool mSkiaValid = false;
// Whether or not Skia layering over the WebGL context is enabled
bool mSkiaLayer = false;
// Whether or not the WebGL context has valid contents and is being drawn to
bool mWebglValid = true;
// Whether or not the clip state has changed since last used by SharedContext.
bool mClipChanged = true;
// Whether or not the clip state needs to be refreshed. Sometimes the clip
// state may be overwritten and require a refresh later, even though it has
// not changed.
bool mRefreshClipState = true;
// The framebuffer has been modified and should be copied to the swap chain.
bool mNeedsPresent = true;
// The number of layers currently pushed.
int32_t mLayerDepth = 0;
RefPtr<TextureHandle> mSnapshotTexture;
// Store a log of clips currently pushed so that they can be used to init
// the clip state of temporary DTs.
struct ClipStack {
Matrix mTransform;
Rect mRect;
RefPtr<const Path> mPath;
};
std::vector<ClipStack> mClipStack;
// UsageProfile stores per-frame counters for significant profiling events
// that assist in determining whether acceleration should still be used for
// a Canvas2D user.
struct UsageProfile {
uint32_t mFailedFrames = 0;
uint32_t mFrameCount = 0;
uint32_t mCacheMisses = 0;
uint32_t mCacheHits = 0;
uint32_t mUncachedDraws = 0;
uint32_t mLayers = 0;
uint32_t mReadbacks = 0;
uint32_t mFallbacks = 0;
void BeginFrame();
void EndFrame();
bool RequiresRefresh() const;
void OnCacheMiss() { ++mCacheMisses; }
void OnCacheHit() { ++mCacheHits; }
void OnUncachedDraw() { ++mUncachedDraws; }
void OnLayer() { ++mLayers; }
void OnReadback() { ++mReadbacks; }
void OnFallback() { ++mFallbacks; }
};
UsageProfile mProfile;
// SharedContext stores most of the actual WebGL state that may be used by
// any number of DrawTargetWebgl's that use it. Foremost, it holds the actual
// WebGL client context, programs, and buffers for mapping to WebGL.
// Secondarily, it holds shared caches for surfaces, glyphs, paths, and
// shadows so that each DrawTargetWebgl does not require its own cache. It is
// important that SetTarget is called to install the current DrawTargetWebgl
// before actually using the SharedContext, as the individual framebuffers
// and viewport are still maintained in DrawTargetWebgl itself.
class SharedContext : public mozilla::RefCounted<SharedContext>,
public mozilla::SupportsWeakPtr {
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(SharedContext)
SharedContext();
~SharedContext();
WeakPtr<DrawTargetWebgl> mCurrentTarget;
IntSize mViewportSize;
IntRect mClipRect;
RefPtr<ClientWebGLContext> mWebgl;
// Avoid spurious state changes by caching last used state.
RefPtr<WebGLProgramJS> mLastProgram;
RefPtr<WebGLTextureJS> mLastTexture;
RefPtr<WebGLTextureJS> mLastClipMask;
bool mDirtyViewport = true;
bool mDirtyAA = true;
// WebGL shader resources
RefPtr<WebGLBufferJS> mVertexBuffer;
RefPtr<WebGLVertexArrayJS> mVertexArray;
RefPtr<WebGLProgramJS> mSolidProgram;
RefPtr<WebGLUniformLocationJS> mSolidProgramViewport;
RefPtr<WebGLUniformLocationJS> mSolidProgramAA;
RefPtr<WebGLUniformLocationJS> mSolidProgramTransform;
RefPtr<WebGLUniformLocationJS> mSolidProgramColor;
RefPtr<WebGLUniformLocationJS> mSolidProgramClipMask;
RefPtr<WebGLProgramJS> mImageProgram;
RefPtr<WebGLUniformLocationJS> mImageProgramViewport;
RefPtr<WebGLUniformLocationJS> mImageProgramAA;
RefPtr<WebGLUniformLocationJS> mImageProgramTransform;
RefPtr<WebGLUniformLocationJS> mImageProgramTexMatrix;
RefPtr<WebGLUniformLocationJS> mImageProgramTexBounds;
RefPtr<WebGLUniformLocationJS> mImageProgramColor;
RefPtr<WebGLUniformLocationJS> mImageProgramSwizzle;
RefPtr<WebGLUniformLocationJS> mImageProgramSampler;
RefPtr<WebGLUniformLocationJS> mImageProgramClipMask;
// Scratch framebuffer used to wrap textures for miscellaneous utility ops.
RefPtr<WebGLFramebufferJS> mScratchFramebuffer;
// Buffer filled with zero data for initializing textures.
RefPtr<WebGLBufferJS> mZeroBuffer;
size_t mZeroSize = 0;
// 1x1 texture with solid white mask for disabling clipping
RefPtr<WebGLTextureJS> mNoClipMask;
uint32_t mMaxTextureSize = 0;
// The current blending operation.
CompositionOp mLastCompositionOp = CompositionOp::OP_SOURCE;
// The constant blend color used for the blending operation.
Maybe<DeviceColor> mLastBlendColor;
// A most-recently-used list of allocated texture handles.
LinkedList<RefPtr<TextureHandle>> mTextureHandles;
size_t mNumTextureHandles = 0;
// User data key linking a SourceSurface with its TextureHandle.
UserDataKey mTextureHandleKey = {0};
// User data key linking a SourceSurface with its shadow blur TextureHandle.
UserDataKey mShadowTextureKey = {0};
// User data key linking a ScaledFont with its GlyphCache.
UserDataKey mGlyphCacheKey = {0};
// List of all GlyphCaches currently allocated to fonts.
LinkedList<GlyphCache> mGlyphCaches;
// Cache of rasterized paths.
UniquePtr<PathCache> mPathCache;
// Collection of allocated shared texture pages that may be shared amongst
// many handles.
std::vector<RefPtr<SharedTexture>> mSharedTextures;
// Collection of allocated standalone textures that have a single assigned
// handle.
std::vector<RefPtr<StandaloneTexture>> mStandaloneTextures;
size_t mUsedTextureMemory = 0;
size_t mTotalTextureMemory = 0;
// The total reserved memory for empty texture pages that are kept around
// for future allocations.
size_t mEmptyTextureMemory = 0;
// A memory pressure event may signal from another thread that caches should
// be cleared if possible.
Atomic<bool> mShouldClearCaches;
// Whether the Shmem is currently being processed by the remote side. If so,
// we need to wait for processing to complete before any further commands
// modifying the Skia DT can proceed.
bool mWaitForShmem = false;
const Matrix& GetTransform() const { return mCurrentTarget->mTransform; }
bool IsContextLost() const;
bool Initialize();
bool CreateShaders();
void SetBlendState(CompositionOp aOp,
const Maybe<DeviceColor>& aBlendColor = Nothing());
void SetClipRect(const IntRect& aClipRect) { mClipRect = aClipRect; }
bool SetClipMask(const RefPtr<WebGLTextureJS>& aTex);
bool SetNoClipMask();
bool HasClipMask() const {
return mLastClipMask && mLastClipMask != mNoClipMask;
}
bool IsCurrentTarget(DrawTargetWebgl* aDT) const {
return aDT == mCurrentTarget;
}
bool SetTarget(DrawTargetWebgl* aDT);
// Reset the current target.
void ClearTarget() { mCurrentTarget = nullptr; }
// Reset the last used texture to force binding next use.
void ClearLastTexture();
bool SupportsPattern(const Pattern& aPattern);
void SetTexFilter(WebGLTextureJS* aTex, bool aFilter);
void InitTexParameters(WebGLTextureJS* aTex, bool aFilter = true);
bool ReadInto(uint8_t* aDstData, int32_t aDstStride, SurfaceFormat aFormat,
const IntRect& aBounds, TextureHandle* aHandle = nullptr);
already_AddRefed<DataSourceSurface> ReadSnapshot(
TextureHandle* aHandle = nullptr);
already_AddRefed<TextureHandle> WrapSnapshot(const IntSize& aSize,
SurfaceFormat aFormat,
RefPtr<WebGLTextureJS> aTex);
already_AddRefed<TextureHandle> CopySnapshot();
already_AddRefed<WebGLTextureJS> GetCompatibleSnapshot(
SourceSurface* aSurface);
bool UploadSurface(DataSourceSurface* aData, SurfaceFormat aFormat,
const IntRect& aSrcRect, const IntPoint& aDstOffset,
bool aInit, bool aZero = false);
bool DrawRectAccel(const Rect& aRect, const Pattern& aPattern,
const DrawOptions& aOptions,
Maybe<DeviceColor> aMaskColor = Nothing(),
RefPtr<TextureHandle>* aHandle = nullptr,
bool aTransformed = true, bool aClipped = true,
bool aAccelOnly = false, bool aForceUpdate = false,
const StrokeOptions* aStrokeOptions = nullptr);
bool DrawPathAccel(const Path* aPath, const Pattern& aPattern,
const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions = nullptr,
const ShadowOptions* aShadow = nullptr,
bool aCacheable = true);
bool DrawGlyphsAccel(ScaledFont* aFont, const GlyphBuffer& aBuffer,
const Pattern& aPattern, const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions,
bool aUseSubpixelAA);
void PruneTextureHandle(const RefPtr<TextureHandle>& aHandle);
bool PruneTextureMemory(size_t aMargin = 0, bool aPruneUnused = true);
bool RemoveSharedTexture(const RefPtr<SharedTexture>& aTexture);
bool RemoveStandaloneTexture(const RefPtr<StandaloneTexture>& aTexture);
void UnlinkSurfaceTextures();
void UnlinkSurfaceTexture(const RefPtr<TextureHandle>& aHandle);
void UnlinkGlyphCaches();
void OnMemoryPressure();
void ClearAllTextures();
void ClearEmptyTextureMemory();
void ClearCachesIfNecessary();
void WaitForShmem();
};
RefPtr<SharedContext> mSharedContext;
static MOZ_THREAD_LOCAL(SharedContext*) sSharedContext;
public:
DrawTargetWebgl();
~DrawTargetWebgl();
static already_AddRefed<DrawTargetWebgl> Create(const IntSize& aSize,
SurfaceFormat aFormat);
bool Init(const IntSize& aSize, SurfaceFormat aFormat);
bool IsValid() const override;
DrawTargetType GetType() const override {
return DrawTargetType::HARDWARE_RASTER;
}
BackendType GetBackendType() const override { return BackendType::WEBGL; }
IntSize GetSize() const override { return mSize; }
already_AddRefed<SourceSurface> GetDataSnapshot();
already_AddRefed<SourceSurface> Snapshot() override;
already_AddRefed<SourceSurface> GetBackingSurface() override;
void DetachAllSnapshots() override;
void BeginFrame(const IntRect& aPersistedRect);
void EndFrame();
bool RequiresRefresh() const { return mProfile.RequiresRefresh(); }
bool LockBits(uint8_t** aData, IntSize* aSize, int32_t* aStride,
SurfaceFormat* aFormat, IntPoint* aOrigin = nullptr) override;
void ReleaseBits(uint8_t* aData) override;
void Flush() override {}
void DrawSurface(
SourceSurface* aSurface, const Rect& aDest, const Rect& aSource,
const DrawSurfaceOptions& aSurfOptions = DrawSurfaceOptions(),
const DrawOptions& aOptions = DrawOptions()) override;
void DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
const Point& aDestPoint,
const DrawOptions& aOptions = DrawOptions()) override;
void DrawSurfaceWithShadow(SourceSurface* aSurface, const Point& aDest,
const ShadowOptions& aShadow,
CompositionOp aOperator) override;
void DrawShadow(const Path* aPath, const Pattern& aPattern,
const ShadowOptions& aShadow, const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions = nullptr) override;
void ClearRect(const Rect& aRect) override;
void CopySurface(SourceSurface* aSurface, const IntRect& aSourceRect,
const IntPoint& aDestination) override;
void FillRect(const Rect& aRect, const Pattern& aPattern,
const DrawOptions& aOptions = DrawOptions()) override;
bool StrokeRectAccel(const Rect& aRect, const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions);
void StrokeRect(const Rect& aRect, const Pattern& aPattern,
const StrokeOptions& aStrokeOptions = StrokeOptions(),
const DrawOptions& aOptions = DrawOptions()) override;
bool StrokeLineAccel(const Point& aStart, const Point& aEnd,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions);
void StrokeLine(const Point& aStart, const Point& aEnd,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions = StrokeOptions(),
const DrawOptions& aOptions = DrawOptions()) override;
void Stroke(const Path* aPath, const Pattern& aPattern,
const StrokeOptions& aStrokeOptions = StrokeOptions(),
const DrawOptions& aOptions = DrawOptions()) override;
void Fill(const Path* aPath, const Pattern& aPattern,
const DrawOptions& aOptions = DrawOptions()) override;
void SetPermitSubpixelAA(bool aPermitSubpixelAA) override;
void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
const Pattern& aPattern,
const DrawOptions& aOptions = DrawOptions()) override;
void StrokeGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions = StrokeOptions(),
const DrawOptions& aOptions = DrawOptions()) override;
void Mask(const Pattern& aSource, const Pattern& aMask,
const DrawOptions& aOptions = DrawOptions()) override;
void MaskSurface(const Pattern& aSource, SourceSurface* aMask, Point aOffset,
const DrawOptions& aOptions = DrawOptions()) override;
bool Draw3DTransformedSurface(SourceSurface* aSurface,
const Matrix4x4& aMatrix) override;
void PushClip(const Path* aPath) override;
void PushClipRect(const Rect& aRect) override;
void PushDeviceSpaceClipRects(const IntRect* aRects,
uint32_t aCount) override;
void PopClip() override;
void PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask,
const Matrix& aMaskTransform,
const IntRect& aBounds = IntRect(),
bool aCopyBackground = false) override;
void PushLayerWithBlend(
bool aOpaque, Float aOpacity, SourceSurface* aMask,
const Matrix& aMaskTransform, const IntRect& aBounds = IntRect(),
bool aCopyBackground = false,
CompositionOp aCompositionOp = CompositionOp::OP_OVER) override;
void PopLayer() override;
already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(
unsigned char* aData, const IntSize& aSize, int32_t aStride,
SurfaceFormat aFormat) const override;
already_AddRefed<SourceSurface> OptimizeSourceSurface(
SourceSurface* aSurface) const override;
already_AddRefed<SourceSurface> OptimizeSourceSurfaceForUnknownAlpha(
SourceSurface* aSurface) const override;
already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface(
const NativeSurface& aSurface) const override;
already_AddRefed<DrawTarget> CreateSimilarDrawTarget(
const IntSize& aSize, SurfaceFormat aFormat) const override;
bool CanCreateSimilarDrawTarget(const IntSize& aSize,
SurfaceFormat aFormat) const override;
RefPtr<DrawTarget> CreateClippedDrawTarget(const Rect& aBounds,
SurfaceFormat aFormat) override;
already_AddRefed<PathBuilder> CreatePathBuilder(
FillRule aFillRule = FillRule::FILL_WINDING) const override;
already_AddRefed<GradientStops> CreateGradientStops(
GradientStop* aStops, uint32_t aNumStops,
ExtendMode aExtendMode = ExtendMode::CLAMP) const override;
already_AddRefed<FilterNode> CreateFilter(FilterType aType) override;
void SetTransform(const Matrix& aTransform) override;
void* GetNativeSurface(NativeSurfaceType aType) override;
Maybe<layers::SurfaceDescriptor> GetFrontBuffer();
void OnMemoryPressure() { mSharedContext->OnMemoryPressure(); }
operator std::string() const {
std::stringstream stream;
stream << "DrawTargetWebgl(" << this << ")";
return stream.str();
}
private:
bool SupportsPattern(const Pattern& aPattern) {
return mSharedContext->SupportsPattern(aPattern);
}
bool SetSimpleClipRect();
bool GenerateComplexClipMask();
bool PrepareContext(bool aClipped = true);
void DrawRectFallback(const Rect& aRect, const Pattern& aPattern,
const DrawOptions& aOptions,
Maybe<DeviceColor> aMaskColor, bool aTransform,
bool aClipped, const StrokeOptions* aStrokeOptions);
bool DrawRect(const Rect& aRect, const Pattern& aPattern,
const DrawOptions& aOptions,
Maybe<DeviceColor> aMaskColor = Nothing(),
RefPtr<TextureHandle>* aHandle = nullptr,
bool aTransformed = true, bool aClipped = true,
bool aAccelOnly = false, bool aForceUpdate = false,
const StrokeOptions* aStrokeOptions = nullptr);
void DrawPath(const Path* aPath, const Pattern& aPattern,
const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions = nullptr);
bool MarkChanged();
void ReadIntoSkia();
void FlattenSkia();
bool FlushFromSkia();
void WaitForShmem() {
if (mSharedContext->mWaitForShmem) {
mSharedContext->WaitForShmem();
}
}
void MarkSkiaChanged() {
WaitForShmem();
if (!mSkiaValid) {
ReadIntoSkia();
} else if (mSkiaLayer) {
FlattenSkia();
}
mWebglValid = false;
}
void MarkSkiaChanged(const DrawOptions& aOptions);
bool ShouldUseSubpixelAA(ScaledFont* aFont, const DrawOptions& aOptions);
bool ReadInto(uint8_t* aDstData, int32_t aDstStride);
already_AddRefed<DataSourceSurface> ReadSnapshot();
already_AddRefed<TextureHandle> CopySnapshot();
void ClearSnapshot(bool aCopyOnWrite = true, bool aNeedHandle = false);
bool CreateFramebuffer();
// PrepareContext may sometimes be used recursively. When this occurs, ensure
// that clip state is restored after the context is used.
struct AutoRestoreContext {
DrawTargetWebgl* mTarget;
IntRect mClipRect;
RefPtr<WebGLTextureJS> mLastClipMask;
explicit AutoRestoreContext(DrawTargetWebgl* aTarget);
~AutoRestoreContext();
};
};
} // namespace gfx
} // namespace mozilla
#endif // _MOZILLA_GFX_DRAWTARGETWEBGL_H