/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * 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 "mozilla/layers/PLayersParent.h" #include "gfxImageSurface.h" #include "GLContext.h" #include "gfxUtils.h" #include "BasicLayersImpl.h" using namespace mozilla::gfx; namespace mozilla { namespace layers { class BasicCanvasLayer : public CanvasLayer, public BasicImplData { public: BasicCanvasLayer(BasicLayerManager* aLayerManager) : CanvasLayer(aLayerManager, static_cast(this)) { MOZ_COUNT_CTOR(BasicCanvasLayer); } virtual ~BasicCanvasLayer() { MOZ_COUNT_DTOR(BasicCanvasLayer); } virtual void SetVisibleRegion(const nsIntRegion& aRegion) { NS_ASSERTION(BasicManager()->InConstruction(), "Can only set properties in construction phase"); CanvasLayer::SetVisibleRegion(aRegion); } virtual void Initialize(const Data& aData); virtual void Paint(gfxContext* aContext, Layer* aMaskLayer); virtual void PaintWithOpacity(gfxContext* aContext, float aOpacity, Layer* aMaskLayer); protected: BasicLayerManager* BasicManager() { return static_cast(mManager); } void UpdateSurface(gfxASurface* aDestSurface = nsnull, Layer* aMaskLayer = nsnull); nsRefPtr mSurface; nsRefPtr mGLContext; mozilla::RefPtr mDrawTarget; PRUint32 mCanvasFramebuffer; bool mGLBufferIsPremultiplied; bool mNeedsYFlip; nsRefPtr mCachedTempSurface; gfxIntSize mCachedSize; gfxImageFormat mCachedFormat; gfxImageSurface* GetTempSurface(const gfxIntSize& aSize, const gfxImageFormat aFormat) { if (!mCachedTempSurface || aSize.width != mCachedSize.width || aSize.height != mCachedSize.height || aFormat != mCachedFormat) { mCachedTempSurface = new gfxImageSurface(aSize, aFormat); mCachedSize = aSize; mCachedFormat = aFormat; } return mCachedTempSurface; } void DiscardTempSurface() { mCachedTempSurface = nsnull; } }; void BasicCanvasLayer::Initialize(const Data& aData) { NS_ASSERTION(mSurface == nsnull, "BasicCanvasLayer::Initialize called twice!"); if (aData.mSurface) { mSurface = aData.mSurface; NS_ASSERTION(aData.mGLContext == nsnull, "CanvasLayer can't have both surface and GLContext"); mNeedsYFlip = false; } else if (aData.mGLContext) { NS_ASSERTION(aData.mGLContext->IsOffscreen(), "canvas gl context isn't offscreen"); mGLContext = aData.mGLContext; mGLBufferIsPremultiplied = aData.mGLBufferIsPremultiplied; mCanvasFramebuffer = mGLContext->GetOffscreenFBO(); mNeedsYFlip = true; } else if (aData.mDrawTarget) { mDrawTarget = aData.mDrawTarget; mSurface = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mDrawTarget); mNeedsYFlip = false; } else { NS_ERROR("CanvasLayer created without mSurface, mDrawTarget or mGLContext?"); } mBounds.SetRect(0, 0, aData.mSize.width, aData.mSize.height); } void BasicCanvasLayer::UpdateSurface(gfxASurface* aDestSurface, Layer* aMaskLayer) { if (mDrawTarget) { mDrawTarget->Flush(); } if (!mGLContext && aDestSurface) { nsRefPtr tmpCtx = new gfxContext(aDestSurface); tmpCtx->SetOperator(gfxContext::OPERATOR_SOURCE); BasicCanvasLayer::PaintWithOpacity(tmpCtx, 1.0f, aMaskLayer); return; } if (!mDirty) return; mDirty = false; if (mGLContext) { if (aDestSurface && aDestSurface->GetType() != gfxASurface::SurfaceTypeImage) { NS_ASSERTION(aDestSurface->GetType() == gfxASurface::SurfaceTypeImage, "Destination surface must be ImageSurface type"); return; } // We need to read from the GLContext mGLContext->MakeCurrent(); #if defined (MOZ_X11) && defined (MOZ_EGL_XRENDER_COMPOSITE) mGLContext->GuaranteeResolve(); gfxASurface* offscreenSurface = mGLContext->GetOffscreenPixmapSurface(); // XRender can only blend premuliplied alpha, so only allow xrender // path if we have premultiplied alpha or opaque content. if (offscreenSurface && (mGLBufferIsPremultiplied || (GetContentFlags() & CONTENT_OPAQUE))) { mSurface = offscreenSurface; mNeedsYFlip = false; return; } #endif nsRefPtr isurf; if (aDestSurface) { DiscardTempSurface(); isurf = static_cast(aDestSurface); } else { nsIntSize size(mBounds.width, mBounds.height); gfxImageFormat format = (GetContentFlags() & CONTENT_OPAQUE) ? gfxASurface::ImageFormatRGB24 : gfxASurface::ImageFormatARGB32; isurf = GetTempSurface(size, format); } if (!isurf || isurf->CairoStatus() != 0) { return; } NS_ASSERTION(isurf->Stride() == mBounds.width * 4, "gfxImageSurface stride isn't what we expect!"); PRUint32 currentFramebuffer = 0; mGLContext->fGetIntegerv(LOCAL_GL_FRAMEBUFFER_BINDING, (GLint*)¤tFramebuffer); // Make sure that we read pixels from the correct framebuffer, regardless // of what's currently bound. if (currentFramebuffer != mCanvasFramebuffer) mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mCanvasFramebuffer); mGLContext->ReadPixelsIntoImageSurface(0, 0, mBounds.width, mBounds.height, isurf); // Put back the previous framebuffer binding. if (currentFramebuffer != mCanvasFramebuffer) mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, currentFramebuffer); // If the underlying GLContext doesn't have a framebuffer into which // premultiplied values were written, we have to do this ourselves here. // Note that this is a WebGL attribute; GL itself has no knowledge of // premultiplied or unpremultiplied alpha. if (!mGLBufferIsPremultiplied) gfxUtils::PremultiplyImageSurface(isurf); // stick our surface into mSurface, so that the Paint() path is the same if (!aDestSurface) { mSurface = isurf; } } } void BasicCanvasLayer::Paint(gfxContext* aContext, Layer* aMaskLayer) { if (IsHidden()) return; UpdateSurface(); FireDidTransactionCallback(); PaintWithOpacity(aContext, GetEffectiveOpacity(), aMaskLayer); } void BasicCanvasLayer::PaintWithOpacity(gfxContext* aContext, float aOpacity, Layer* aMaskLayer) { NS_ASSERTION(BasicManager()->InDrawing(), "Can only draw in drawing phase"); if (!mSurface) { NS_WARNING("No valid surface to draw!"); return; } nsRefPtr pat = new gfxPattern(mSurface); pat->SetFilter(mFilter); pat->SetExtend(gfxPattern::EXTEND_PAD); gfxMatrix m; if (mNeedsYFlip) { m = aContext->CurrentMatrix(); aContext->Translate(gfxPoint(0.0, mBounds.height)); aContext->Scale(1.0, -1.0); } // If content opaque, then save off current operator and set to source. // This ensures that alpha is not applied even if the source surface // has an alpha channel gfxContext::GraphicsOperator savedOp; if (GetContentFlags() & CONTENT_OPAQUE) { savedOp = aContext->CurrentOperator(); aContext->SetOperator(gfxContext::OPERATOR_SOURCE); } AutoSetOperator setOperator(aContext, GetOperator()); aContext->NewPath(); // No need to snap here; our transform is already set up to snap our rect aContext->Rectangle(gfxRect(0, 0, mBounds.width, mBounds.height)); aContext->SetPattern(pat); FillWithMask(aContext, aOpacity, aMaskLayer); #if defined (MOZ_X11) && defined (MOZ_EGL_XRENDER_COMPOSITE) if (mGLContext) { // Wait for X to complete all operations before continuing // Otherwise gl context could get cleared before X is done. mGLContext->WaitNative(); } #endif // Restore surface operator if (GetContentFlags() & CONTENT_OPAQUE) { aContext->SetOperator(savedOp); } if (mNeedsYFlip) { aContext->SetMatrix(m); } } class BasicShadowableCanvasLayer : public BasicCanvasLayer, public BasicShadowableLayer { public: BasicShadowableCanvasLayer(BasicShadowLayerManager* aManager) : BasicCanvasLayer(aManager), mBufferIsOpaque(false) { MOZ_COUNT_CTOR(BasicShadowableCanvasLayer); } virtual ~BasicShadowableCanvasLayer() { DestroyBackBuffer(); MOZ_COUNT_DTOR(BasicShadowableCanvasLayer); } virtual void Initialize(const Data& aData); virtual void Paint(gfxContext* aContext, Layer* aMaskLayer); virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) { aAttrs = CanvasLayerAttributes(mFilter); } virtual Layer* AsLayer() { return this; } virtual ShadowableLayer* AsShadowableLayer() { return this; } virtual void SetBackBuffer(const SurfaceDescriptor& aBuffer) { mBackBuffer = aBuffer; } virtual void Disconnect() { mBackBuffer = SurfaceDescriptor(); BasicShadowableLayer::Disconnect(); } void DestroyBackBuffer() { if (IsSurfaceDescriptorValid(mBackBuffer)) { BasicManager()->ShadowLayerForwarder::DestroySharedSurface(&mBackBuffer); mBackBuffer = SurfaceDescriptor(); } } private: BasicShadowLayerManager* BasicManager() { return static_cast(mManager); } SurfaceDescriptor mBackBuffer; bool mBufferIsOpaque; }; void BasicShadowableCanvasLayer::Initialize(const Data& aData) { BasicCanvasLayer::Initialize(aData); if (!HasShadow()) return; // XXX won't get here currently; need to figure out what to do on // canvas resizes if (IsSurfaceDescriptorValid(mBackBuffer)) { nsRefPtr backSurface = BasicManager()->OpenDescriptor(mBackBuffer); if (gfxIntSize(mBounds.width, mBounds.height) != backSurface->GetSize()) { DestroyBackBuffer(); } } } void BasicShadowableCanvasLayer::Paint(gfxContext* aContext, Layer* aMaskLayer) { if (!HasShadow()) { BasicCanvasLayer::Paint(aContext, aMaskLayer); return; } bool isOpaque = (GetContentFlags() & CONTENT_OPAQUE); if (!IsSurfaceDescriptorValid(mBackBuffer) || isOpaque != mBufferIsOpaque) { DestroyBackBuffer(); mBufferIsOpaque = isOpaque; if (!BasicManager()->AllocBuffer( gfxIntSize(mBounds.width, mBounds.height), isOpaque ? gfxASurface::CONTENT_COLOR : gfxASurface::CONTENT_COLOR_ALPHA, &mBackBuffer)) NS_RUNTIMEABORT("creating CanvasLayer back buffer failed!"); } nsRefPtr backSurface = BasicManager()->OpenDescriptor(mBackBuffer); if (aMaskLayer) { static_cast(aMaskLayer->ImplData()) ->Paint(aContext, nsnull); } UpdateSurface(backSurface, nsnull); FireDidTransactionCallback(); BasicManager()->PaintedCanvas(BasicManager()->Hold(this), mNeedsYFlip ? true : false, mBackBuffer); } class BasicShadowCanvasLayer : public ShadowCanvasLayer, public BasicImplData { public: BasicShadowCanvasLayer(BasicShadowLayerManager* aLayerManager) : ShadowCanvasLayer(aLayerManager, static_cast(this)) { MOZ_COUNT_CTOR(BasicShadowCanvasLayer); } virtual ~BasicShadowCanvasLayer() { MOZ_COUNT_DTOR(BasicShadowCanvasLayer); } virtual void Disconnect() { DestroyFrontBuffer(); ShadowCanvasLayer::Disconnect(); } virtual void Initialize(const Data& aData); void Swap(const CanvasSurface& aNewFront, bool needYFlip, CanvasSurface* aNewBack); virtual void DestroyFrontBuffer() { if (IsSurfaceDescriptorValid(mFrontSurface)) { mAllocator->DestroySharedSurface(&mFrontSurface); } } virtual void Paint(gfxContext* aContext, Layer* aMaskLayer); private: BasicShadowLayerManager* BasicManager() { return static_cast(mManager); } SurfaceDescriptor mFrontSurface; bool mNeedsYFlip; }; void BasicShadowCanvasLayer::Initialize(const Data& aData) { NS_RUNTIMEABORT("Incompatibe surface type"); } void BasicShadowCanvasLayer::Swap(const CanvasSurface& aNewFront, bool needYFlip, CanvasSurface* aNewBack) { nsRefPtr surface = BasicManager()->OpenDescriptor(aNewFront); // Destroy mFrontBuffer if size different gfxIntSize sz = surface->GetSize(); bool surfaceConfigChanged = sz != gfxIntSize(mBounds.width, mBounds.height); if (IsSurfaceDescriptorValid(mFrontSurface)) { nsRefPtr front = BasicManager()->OpenDescriptor(mFrontSurface); surfaceConfigChanged = surfaceConfigChanged || surface->GetContentType() != front->GetContentType(); } if (surfaceConfigChanged) { DestroyFrontBuffer(); mBounds.SetRect(0, 0, sz.width, sz.height); } mNeedsYFlip = needYFlip; // If mFrontBuffer if (IsSurfaceDescriptorValid(mFrontSurface)) { *aNewBack = mFrontSurface; } else { *aNewBack = null_t(); } mFrontSurface = aNewFront.get_SurfaceDescriptor(); } void BasicShadowCanvasLayer::Paint(gfxContext* aContext, Layer* aMaskLayer) { NS_ASSERTION(BasicManager()->InDrawing(), "Can only draw in drawing phase"); if (!IsSurfaceDescriptorValid(mFrontSurface)) { return; } nsRefPtr surface = BasicManager()->OpenDescriptor(mFrontSurface); nsRefPtr pat = new gfxPattern(surface); pat->SetFilter(mFilter); pat->SetExtend(gfxPattern::EXTEND_PAD); gfxRect r(0, 0, mBounds.width, mBounds.height); gfxMatrix m; if (mNeedsYFlip) { m = aContext->CurrentMatrix(); aContext->Translate(gfxPoint(0.0, mBounds.height)); aContext->Scale(1.0, -1.0); } AutoSetOperator setOperator(aContext, GetOperator()); aContext->NewPath(); // No need to snap here; our transform has already taken care of it aContext->Rectangle(r); aContext->SetPattern(pat); FillWithMask(aContext, GetEffectiveOpacity(), aMaskLayer); if (mNeedsYFlip) { aContext->SetMatrix(m); } } already_AddRefed BasicLayerManager::CreateCanvasLayer() { NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); nsRefPtr layer = new BasicCanvasLayer(this); return layer.forget(); } already_AddRefed BasicShadowLayerManager::CreateCanvasLayer() { NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); nsRefPtr layer = new BasicShadowableCanvasLayer(this); MAYBE_CREATE_SHADOW(Canvas); return layer.forget(); } already_AddRefed BasicShadowLayerManager::CreateShadowCanvasLayer() { NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); nsRefPtr layer = new BasicShadowCanvasLayer(this); return layer.forget(); } } }