Bug 1754130 - Support presenting a WebGLFramebuffer to its own swap chain without opaque FB. r=aosmond,jgilbert

Most of the support for presenting a WebGLFramebuffer to a swap chain existed as part of the
mechanism for opaque WebXR framebuffer support. However, such "opaque" framebuffer are meant
to be opaque in the sense that their attachments can't be inspected or changed, which does
not provide the requisite level of control for efficiently implementing Canvas2D snapshots.

To this end, the existing Present mechanism is slightly extended to allow presenting to the
swap chain already present in WebGLFramebuffer without the existence of a corresponding
MozFramebuffer.

This also fixes a bug in that AsWebgl() was no longer being utilized in CanvasRenderer, such
that a new mechanism that routed GetFrontBuffer() was needed to fix the code rot.

There are also some efforts to remove a couple redundant copies I noticed in profiles along
the way.

Differential Revision: https://phabricator.services.mozilla.com/D138119
This commit is contained in:
Lee Salzman 2022-02-11 15:36:30 +00:00
parent 8a34e8c65e
commit 4fb52bf9bc
15 changed files with 201 additions and 51 deletions

View File

@ -1508,6 +1508,14 @@ bool CanvasRenderingContext2D::TryBasicTarget(
return true;
}
Maybe<SurfaceDescriptor> CanvasRenderingContext2D::GetFrontBuffer(
WebGLFramebufferJS*, const bool webvr) {
if (mBufferProvider) {
return mBufferProvider->GetFrontBuffer();
}
return Nothing();
}
PresShell* CanvasRenderingContext2D::GetPresShell() {
if (mCanvasElement) {
return mCanvasElement->OwnerDoc()->GetPresShell();

View File

@ -99,6 +99,9 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
return mBufferProvider;
}
Maybe<layers::SurfaceDescriptor> GetFrontBuffer(
WebGLFramebufferJS*, const bool webvr = false) override;
void Save() override;
void Restore() override;
void Scale(double aX, double aY, mozilla::ErrorResult& aError) override;

View File

@ -368,13 +368,7 @@ void ClientWebGLContext::Run(Args&&... args) const {
// ------------------------- Composition, etc -------------------------
void ClientWebGLContext::OnBeforePaintTransaction() {
const RefPtr<layers::ImageBridgeChild> imageBridge =
layers::ImageBridgeChild::GetSingleton();
const auto texType = layers::TexTypeForWebgl(imageBridge);
Present(nullptr, texType);
}
void ClientWebGLContext::OnBeforePaintTransaction() { Present(nullptr); }
void ClientWebGLContext::EndComposition() {
// Mark ourselves as no longer invalidated.
@ -383,6 +377,15 @@ void ClientWebGLContext::EndComposition() {
// -
void ClientWebGLContext::Present(WebGLFramebufferJS* const xrFb,
const bool webvr) {
const RefPtr<layers::ImageBridgeChild> imageBridge =
layers::ImageBridgeChild::GetSingleton();
const auto texType = layers::TexTypeForWebgl(imageBridge);
Present(xrFb, texType, webvr);
}
void ClientWebGLContext::Present(WebGLFramebufferJS* const xrFb,
const layers::TextureType type,
const bool webvr) {

View File

@ -1014,6 +1014,7 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
}
void GetContextAttributes(dom::Nullable<dom::WebGLContextAttributes>& retval);
void Present(WebGLFramebufferJS*, const bool webvr = false);
void Present(WebGLFramebufferJS*, layers::TextureType,
const bool webvr = false);
Maybe<layers::SurfaceDescriptor> GetFrontBuffer(

View File

@ -353,9 +353,11 @@ bool DrawTargetWebgl::SharedContext::SetTarget(DrawTargetWebgl* aDT) {
}
if (aDT != mCurrentTarget) {
mCurrentTarget = aDT;
mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, aDT->mFramebuffer);
mViewportSize = aDT->GetSize();
mWebgl->Viewport(0, 0, mViewportSize.width, mViewportSize.height);
if (aDT) {
mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, aDT->mFramebuffer);
mViewportSize = aDT->GetSize();
mWebgl->Viewport(0, 0, mViewportSize.width, mViewportSize.height);
}
}
return true;
}
@ -648,6 +650,34 @@ already_AddRefed<SourceSurface> DrawTargetWebgl::GetBackingSurface() {
return Snapshot();
}
bool DrawTargetWebgl::CopySnapshotTo(DrawTarget* aDT) {
if (mSkiaValid ||
(mSnapshot &&
(mSnapshot->GetType() != SurfaceType::WEBGL ||
static_cast<SourceSurfaceWebgl*>(mSnapshot.get())->HasReadData()))) {
// There's already a snapshot that is mapped, so just use that.
return false;
}
// Otherwise, attempt to read the data directly into the DT pixels to avoid an
// intermediate copy.
if (!PrepareContext(false)) {
return false;
}
uint8_t* data = nullptr;
IntSize size;
int32_t stride = 0;
SurfaceFormat format = SurfaceFormat::UNKNOWN;
if (!aDT->LockBits(&data, &size, &stride, &format)) {
return false;
}
bool result =
mSharedContext->ReadInto(data, stride, format,
{0, 0, std::min(size.width, mSize.width),
std::min(size.height, mSize.height)});
aDT->ReleaseBits(data);
return result;
}
void DrawTargetWebgl::MarkChanged() {
mSkiaValid = false;
if (RefPtr<SourceSurfaceWebgl> snapshot = ClearSnapshot()) {
@ -1476,7 +1506,7 @@ void StandaloneTexture::Cleanup(DrawTargetWebgl::SharedContext& aContext) {
// Prune a given texture handle and release its associated resources.
void DrawTargetWebgl::SharedContext::PruneTextureHandle(
RefPtr<TextureHandle> aHandle) {
const RefPtr<TextureHandle>& aHandle) {
// Invalidate the handle so nothing will subsequently use its contents.
aHandle->Invalidate();
// If the handle has an associated SourceSurface, unlink it.
@ -2383,12 +2413,28 @@ void DrawTargetWebgl::Flush() {
if (!mWebglValid) {
FlushFromSkia();
}
// Present the WebGL context.
mSharedContext->mWebgl->OnBeforePaintTransaction();
// Present the current WebGL framebuffer.
mSharedContext->mWebgl->Present(mFramebuffer);
// Ensure WebGL state gets reset properly next draw.
mSharedContext->ClearTarget();
mSharedContext->ClearLastTexture();
// Ensure we're not somehow using more than the allowed texture memory.
mSharedContext->PruneTextureMemory();
}
Maybe<layers::SurfaceDescriptor> DrawTargetWebgl::GetFrontBuffer() {
if (mSkiaValid && !mSkiaLayer) {
// If there is a valid Skia snapshot that doesn't depend on WebGL state,
// then don't try to upload it back to WebGL, as that may incur further
// readbacks during compositing.
return Nothing();
}
if (!mWebglValid) {
FlushFromSkia();
}
return mSharedContext->mWebgl->GetFrontBuffer(mFramebuffer);
}
already_AddRefed<DrawTarget> DrawTargetWebgl::CreateSimilarDrawTarget(
const IntSize& aSize, SurfaceFormat aFormat) const {
return mSkia->CreateSimilarDrawTarget(aSize, aFormat);

View File

@ -205,7 +205,7 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
bool FillGlyphsAccel(ScaledFont* aFont, const GlyphBuffer& aBuffer,
const Pattern& aPattern, const DrawOptions& aOptions);
void PruneTextureHandle(RefPtr<TextureHandle> aHandle);
void PruneTextureHandle(const RefPtr<TextureHandle>& aHandle);
bool PruneTextureMemory(size_t aMargin = 0, bool aPruneUnused = true);
bool RemoveSharedTexture(const RefPtr<SharedTexture>& aTexture);
@ -332,6 +332,10 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
void SetTransform(const Matrix& aTransform) override;
void* GetNativeSurface(NativeSurfaceType aType) override;
Maybe<layers::SurfaceDescriptor> GetFrontBuffer();
bool CopySnapshotTo(DrawTarget* aDT);
operator std::string() const {
std::stringstream stream;
stream << "DrawTargetWebgl(" << this << ")";

View File

@ -10,8 +10,7 @@
#include "mozilla/gfx/2D.h"
#include "mozilla/WeakPtr.h"
namespace mozilla {
namespace gfx {
namespace mozilla::gfx {
class DrawTargetWebgl;
class TextureHandle;
@ -38,6 +37,8 @@ class SourceSurfaceWebgl : public DataSourceSurface {
bool Map(MapType aType, MappedSurface* aMappedSurface) override;
void Unmap() override;
bool HasReadData() const { return !!mData; }
private:
friend class DrawTargetWebgl;
@ -63,7 +64,6 @@ class SourceSurfaceWebgl : public DataSourceSurface {
RefPtr<TextureHandle> mHandle;
};
} // namespace gfx
} // namespace mozilla
} // namespace mozilla::gfx
#endif /* MOZILLA_GFX_SOURCESURFACEWEBGL_H_ */

View File

@ -792,7 +792,8 @@ void WebGLContext::OnEndOfFrame() {
}
void WebGLContext::BlitBackbufferToCurDriverFB(
const gl::MozFramebuffer* const source) const {
WebGLFramebuffer* const srcAsWebglFb,
const gl::MozFramebuffer* const srcAsMozFb) const {
DoColorMask(0x0f);
if (mScissorTestEnabled) {
@ -800,25 +801,47 @@ void WebGLContext::BlitBackbufferToCurDriverFB(
}
[&]() {
const auto fb = source ? source : mDefaultFB.get();
// If a MozFramebuffer is supplied, ensure that a WebGLFramebuffer is not
// used since it might not have completeness info, while the MozFramebuffer
// can still supply the needed information.
MOZ_ASSERT(!(srcAsMozFb && srcAsWebglFb));
const auto* mozFb = srcAsMozFb ? srcAsMozFb : mDefaultFB.get();
GLuint fbo = 0;
gfx::IntSize size;
if (srcAsWebglFb) {
fbo = srcAsWebglFb->mGLName;
const auto* info = srcAsWebglFb->GetCompletenessInfo();
MOZ_ASSERT(info);
size = gfx::IntSize(info->width, info->height);
} else {
fbo = mozFb->mFB;
size = mozFb->mSize;
}
if (gl->IsSupported(gl::GLFeature::framebuffer_blit)) {
gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb->mFB);
gl->fBlitFramebuffer(0, 0, fb->mSize.width, fb->mSize.height, 0, 0,
fb->mSize.width, fb->mSize.height,
LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_NEAREST);
gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
gl->fBlitFramebuffer(0, 0, size.width, size.height, 0, 0, size.width,
size.height, LOCAL_GL_COLOR_BUFFER_BIT,
LOCAL_GL_NEAREST);
return;
}
if (mDefaultFB->mSamples &&
gl->IsExtensionSupported(
gl::GLContext::APPLE_framebuffer_multisample)) {
gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb->mFB);
gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
gl->fResolveMultisampleFramebufferAPPLE();
return;
}
gl->BlitHelper()->DrawBlitTextureToFramebuffer(fb->ColorTex(), fb->mSize,
fb->mSize);
GLuint colorTex = 0;
if (srcAsWebglFb) {
const auto& attach = srcAsWebglFb->ColorAttachment0();
MOZ_ASSERT(attach.Texture());
colorTex = attach.Texture()->mGLName;
} else {
colorTex = mozFb->ColorTex();
}
gl->BlitHelper()->DrawBlitTextureToFramebuffer(colorTex, size, size);
}();
if (mScissorTestEnabled) {
@ -837,13 +860,26 @@ constexpr auto MakeArray(Args... args) -> std::array<T, sizeof...(Args)> {
// For an overview of how WebGL compositing works, see:
// https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
bool WebGLContext::PresentInto(gl::SwapChain& swapChain) {
bool WebGLContext::PresentInto(gl::SwapChain& swapChain,
WebGLFramebuffer* const srcFb) {
OnEndOfFrame();
if (!ValidateAndInitFB(nullptr)) return false;
if (!ValidateAndInitFB(srcFb)) return false;
{
auto presenter = swapChain.Acquire(mDefaultFB->mSize);
GLuint fbo = 0;
gfx::IntSize size;
if (srcFb) {
fbo = srcFb->mGLName;
const auto* info = srcFb->GetCompletenessInfo();
MOZ_ASSERT(info);
size = gfx::IntSize(info->width, info->height);
} else {
fbo = mDefaultFB->mFB;
size = mDefaultFB->mSize;
}
auto presenter = swapChain.Acquire(size);
if (!presenter) {
GenerateWarning("Swap chain surface creation failed.");
LoseContext();
@ -853,17 +889,19 @@ bool WebGLContext::PresentInto(gl::SwapChain& swapChain) {
const auto destFb = presenter->Fb();
gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
BlitBackbufferToCurDriverFB();
BlitBackbufferToCurDriverFB(srcFb);
if (!mOptions.preserveDrawingBuffer) {
if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB);
gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
constexpr auto attachments = MakeArray<GLenum>(
LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
attachments.size(), attachments.data());
}
mDefaultFB_IsInvalid = true;
if (!srcFb) {
mDefaultFB_IsInvalid = true;
}
}
#ifdef DEBUG
@ -894,7 +932,7 @@ bool WebGLContext::PresentIntoXR(gl::SwapChain& swapChain,
const auto destFb = presenter->Fb();
gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
BlitBackbufferToCurDriverFB(&fb);
BlitBackbufferToCurDriverFB(nullptr, &fb);
// https://immersive-web.github.io/webxr/#opaque-framebuffer
// Opaque framebuffers will always be cleared regardless of the
@ -918,11 +956,11 @@ void WebGLContext::Present(WebGLFramebuffer* const xrFb,
auto swapChain = webvr ? &mWebVRSwapChain : &mSwapChain;
if (xrFb) {
swapChain = &xrFb->mOpaqueSwapChain;
swapChain = &xrFb->mSwapChain;
}
const gl::MozFramebuffer* maybeFB = nullptr;
if (xrFb) {
swapChain = &xrFb->mOpaqueSwapChain;
swapChain = &xrFb->mSwapChain;
maybeFB = xrFb->mOpaque.get();
} else {
mResolvedDefaultFB = nullptr;
@ -943,7 +981,7 @@ void WebGLContext::Present(WebGLFramebuffer* const xrFb,
if (maybeFB) {
(void)PresentIntoXR(*swapChain, *maybeFB);
} else {
(void)PresentInto(*swapChain);
(void)PresentInto(*swapChain, xrFb);
}
}
@ -951,7 +989,7 @@ Maybe<layers::SurfaceDescriptor> WebGLContext::GetFrontBuffer(
WebGLFramebuffer* const xrFb, const bool webvr) {
auto swapChain = webvr ? &mWebVRSwapChain : &mSwapChain;
if (xrFb) {
swapChain = &xrFb->mOpaqueSwapChain;
swapChain = &xrFb->mSwapChain;
}
const auto& front = swapChain->FrontBuffer();
if (!front) return {};

View File

@ -479,7 +479,7 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
// Present to compositor
private:
bool PresentInto(gl::SwapChain& swapChain);
bool PresentInto(gl::SwapChain& swapChain, WebGLFramebuffer* srcFb = nullptr);
bool PresentIntoXR(gl::SwapChain& swapChain, const gl::MozFramebuffer& xrFb);
public:
@ -1213,7 +1213,8 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
GLenum incompleteFbError = LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION);
void DoColorMask(uint8_t bitmask) const;
void BlitBackbufferToCurDriverFB(
const gl::MozFramebuffer* const source = nullptr) const;
WebGLFramebuffer* const srcAsWebglFb = nullptr,
const gl::MozFramebuffer* const srcAsMozFb = nullptr) const;
bool BindDefaultFBForRead();
// --

View File

@ -144,8 +144,10 @@ class WebGLFramebuffer final : public WebGLContextBoundObject,
const GLuint mGLName;
bool mHasBeenBound = false;
const UniquePtr<gl::MozFramebuffer> mOpaque;
gl::SwapChain mOpaqueSwapChain;
bool mInOpaqueRAF = false;
// Swap chain that may be used to present this framebuffer, for opaque
// framebuffers or other use cases. (e.g. DrawTargetWebgl)
gl::SwapChain mSwapChain;
private:
mutable uint64_t mNumFBStatusInvals = 0;

View File

@ -68,6 +68,28 @@ std::shared_ptr<BorrowedSourceSurface> CanvasRenderer::BorrowSnapshot(
return std::make_shared<BorrowedSourceSurface>(provider, ss);
}
bool CanvasRenderer::CopySnapshotTo(gfx::DrawTarget* aDT,
bool aRequireAlphaPremult) {
auto* const context = mData.GetContext();
if (!context) return false;
if (RefPtr<PersistentBufferProvider> provider =
context->GetBufferProvider()) {
// If we can copy the snapshot directly to the DT, try that first.
if (provider->CopySnapshotTo(aDT)) {
return true;
}
}
// Otherwise, we have to borrow a snapshot before we can copy it to the DT.
auto borrowed = BorrowSnapshot(aRequireAlphaPremult);
if (!borrowed) {
return false;
}
aDT->CopySurface(borrowed->mSurf, borrowed->mSurf->GetRect(), {0, 0});
return true;
}
void CanvasRenderer::FirePreTransactionCallback() const {
if (!mData.mDoPaintCallbacks) return;
const auto context = mData.GetContext();

View File

@ -134,6 +134,9 @@ class CanvasRenderer : public RefCounted<CanvasRenderer> {
std::shared_ptr<BorrowedSourceSurface> BorrowSnapshot(
bool requireAlphaPremult = true) const;
virtual bool CopySnapshotTo(gfx::DrawTarget* aDT,
bool aRequireAlphaPremult = true);
void FirePreTransactionCallback() const;
void FireDidTransactionCallback() const;
};

View File

@ -10,6 +10,7 @@
#include "mozilla/layers/TextureClient.h"
#include "mozilla/layers/TextureForwarder.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/DrawTargetWebgl.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/Maybe.h"
#include "mozilla/StaticPrefs_layers.h"
@ -100,15 +101,20 @@ PersistentBufferProviderAccelerated::PersistentBufferProviderAccelerated(
DrawTarget* aDt)
: PersistentBufferProviderBasic(aDt) {
MOZ_COUNT_CTOR(PersistentBufferProviderAccelerated);
MOZ_ASSERT(aDt->GetBackendType() == BackendType::WEBGL);
}
PersistentBufferProviderAccelerated::~PersistentBufferProviderAccelerated() {
MOZ_COUNT_DTOR(PersistentBufferProviderAccelerated);
}
ClientWebGLContext* PersistentBufferProviderAccelerated::AsWebgl() {
return (ClientWebGLContext*)mDrawTarget->GetNativeSurface(
NativeSurfaceType::WEBGL_CONTEXT);
Maybe<layers::SurfaceDescriptor>
PersistentBufferProviderAccelerated::GetFrontBuffer() {
return static_cast<DrawTargetWebgl*>(mDrawTarget.get())->GetFrontBuffer();
}
bool PersistentBufferProviderAccelerated::CopySnapshotTo(gfx::DrawTarget* aDT) {
return static_cast<DrawTargetWebgl*>(mDrawTarget.get())->CopySnapshotTo(aDT);
}
static already_AddRefed<TextureClient> CreateTexture(

View File

@ -10,6 +10,7 @@
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc
#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed, etc
#include "mozilla/layers/KnowsCompositor.h"
#include "mozilla/layers/LayersSurfaces.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/RefCounted.h"
#include "mozilla/gfx/Types.h"
@ -65,13 +66,17 @@ class PersistentBufferProvider : public RefCounted<PersistentBufferProvider>,
virtual already_AddRefed<gfx::SourceSurface> BorrowSnapshot() = 0;
/**
* Override this if it's possible to read data directly into the DT without
* copying to an intermediate snapshot.
*/
virtual bool CopySnapshotTo(gfx::DrawTarget* aDT) { return false; }
virtual void ReturnSnapshot(
already_AddRefed<gfx::SourceSurface> aSnapshot) = 0;
virtual TextureClient* GetTextureClient() { return nullptr; }
virtual ClientWebGLContext* AsWebgl() { return nullptr; }
virtual void OnShutdown() {}
virtual bool SetKnowsCompositor(KnowsCompositor* aKnowsCompositor) {
@ -88,6 +93,13 @@ class PersistentBufferProvider : public RefCounted<PersistentBufferProvider>,
* costly (cf. bug 1294351).
*/
virtual bool PreservesDrawingState() const = 0;
/**
* Provide a WebGL front buffer for compositing, if available.
*/
virtual Maybe<layers::SurfaceDescriptor> GetFrontBuffer() {
return Nothing();
}
};
class PersistentBufferProviderBasic : public PersistentBufferProvider {
@ -133,7 +145,9 @@ class PersistentBufferProviderAccelerated
bool IsAccelerated() const override { return true; }
ClientWebGLContext* AsWebgl() override;
Maybe<layers::SurfaceDescriptor> GetFrontBuffer() override;
bool CopySnapshotTo(gfx::DrawTarget* aDT) override;
protected:
~PersistentBufferProviderAccelerated() override;

View File

@ -174,10 +174,9 @@ void ShareableCanvasRenderer::UpdateCompositableClient() {
const RefPtr<DrawTarget> dt = tc->BorrowDrawTarget();
const bool requireAlphaPremult = false;
const auto borrowed = BorrowSnapshot(requireAlphaPremult);
if (!borrowed) return nullptr;
dt->CopySurface(borrowed->mSurf, {{0, 0}, size}, {0, 0});
if (!CopySnapshotTo(dt, requireAlphaPremult)) {
return nullptr;
}
}
return tc;