/* -*- Mode: C++; tab-width: 20; 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 "BasicCompositor.h" #include "BasicLayersImpl.h" // for FillRectWithMask #include "TextureHostBasic.h" #include "mozilla/layers/Effects.h" #include "nsIWidget.h" #include "gfx2DGlue.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/gfx/Helpers.h" #include "mozilla/gfx/Tools.h" #include "mozilla/gfx/ssse3-scaler.h" #include "mozilla/layers/ImageDataSerializer.h" #include "mozilla/SSE.h" #include "gfxUtils.h" #include "YCbCrUtils.h" #include #include "ImageContainer.h" #include "gfxPrefs.h" namespace mozilla { using namespace mozilla::gfx; namespace layers { class DataTextureSourceBasic : public DataTextureSource , public TextureSourceBasic { public: virtual const char* Name() const override { return "DataTextureSourceBasic"; } explicit DataTextureSourceBasic(DataSourceSurface* aSurface) : mSurface(aSurface) , mWrappingExistingData(!!aSurface) {} virtual DataTextureSource* AsDataTextureSource() override { // If the texture wraps someone else's memory we'd rather not use it as // a DataTextureSource per say (that is call Update on it). return mWrappingExistingData ? nullptr : this; } virtual TextureSourceBasic* AsSourceBasic() override { return this; } virtual gfx::SourceSurface* GetSurface(DrawTarget* aTarget) override { return mSurface; } SurfaceFormat GetFormat() const override { return mSurface ? mSurface->GetFormat() : gfx::SurfaceFormat::UNKNOWN; } virtual IntSize GetSize() const override { return mSurface ? mSurface->GetSize() : gfx::IntSize(0, 0); } virtual bool Update(gfx::DataSourceSurface* aSurface, nsIntRegion* aDestRegion = nullptr, gfx::IntPoint* aSrcOffset = nullptr) override { MOZ_ASSERT(!mWrappingExistingData); if (mWrappingExistingData) { return false; } mSurface = aSurface; return true; } virtual void DeallocateDeviceData() override { mSurface = nullptr; SetUpdateSerial(0); } public: RefPtr mSurface; bool mWrappingExistingData; }; /** * WrappingTextureSourceYCbCrBasic wraps YUV format BufferTextureHost to defer * yuv->rgb conversion. The conversion happens when GetSurface is called. */ class WrappingTextureSourceYCbCrBasic : public DataTextureSource , public TextureSourceBasic { public: virtual const char* Name() const override { return "WrappingTextureSourceYCbCrBasic"; } explicit WrappingTextureSourceYCbCrBasic(BufferTextureHost* aTexture) : mTexture(aTexture) , mSize(aTexture->GetSize()) , mNeedsUpdate(true) { mFromYCBCR = true; } virtual DataTextureSource* AsDataTextureSource() override { return this; } virtual TextureSourceBasic* AsSourceBasic() override { return this; } virtual WrappingTextureSourceYCbCrBasic* AsWrappingTextureSourceYCbCrBasic() override { return this; } virtual gfx::SourceSurface* GetSurface(DrawTarget* aTarget) override { if (mSurface && !mNeedsUpdate) { return mSurface; } MOZ_ASSERT(mTexture); if (!mTexture) { return nullptr; } if (!mSurface) { mSurface = Factory::CreateDataSourceSurface(mSize, gfx::SurfaceFormat::B8G8R8X8); } if (!mSurface) { return nullptr; } MOZ_ASSERT(mTexture->GetBufferDescriptor().type() == BufferDescriptor::TYCbCrDescriptor); MOZ_ASSERT(mTexture->GetSize() == mSize); mSurface = ImageDataSerializer::DataSourceSurfaceFromYCbCrDescriptor( mTexture->GetBuffer(), mTexture->GetBufferDescriptor().get_YCbCrDescriptor(), mSurface); mNeedsUpdate = false; return mSurface; } SurfaceFormat GetFormat() const override { return gfx::SurfaceFormat::B8G8R8X8; } virtual IntSize GetSize() const override { return mSize; } virtual bool Update(gfx::DataSourceSurface* aSurface, nsIntRegion* aDestRegion = nullptr, gfx::IntPoint* aSrcOffset = nullptr) override { return false; } virtual void DeallocateDeviceData() override { mTexture = nullptr; mSurface = nullptr; SetUpdateSerial(0); } virtual void Unbind() override { mNeedsUpdate = true; } void SetBufferTextureHost(BufferTextureHost* aTexture) override { mTexture = aTexture; mNeedsUpdate = true; } void ConvertAndScale(const SurfaceFormat& aDestFormat, const IntSize& aDestSize, unsigned char* aDestBuffer, int32_t aStride) { MOZ_ASSERT(mTexture); if (!mTexture) { return; } MOZ_ASSERT(mTexture->GetBufferDescriptor().type() == BufferDescriptor::TYCbCrDescriptor); MOZ_ASSERT(mTexture->GetSize() == mSize); ImageDataSerializer::ConvertAndScaleFromYCbCrDescriptor( mTexture->GetBuffer(), mTexture->GetBufferDescriptor().get_YCbCrDescriptor(), aDestFormat, aDestSize, aDestBuffer, aStride); } public: BufferTextureHost* mTexture; const gfx::IntSize mSize; RefPtr mSurface; bool mNeedsUpdate; }; BasicCompositor::BasicCompositor(CompositorBridgeParent* aParent, widget::CompositorWidget* aWidget) : Compositor(aWidget, aParent) , mDidExternalComposition(false) , mIsPendingEndRemoteDrawing(false) { MOZ_COUNT_CTOR(BasicCompositor); mMaxTextureSize = Factory::GetMaxSurfaceSize(gfxVars::ContentBackend()); } BasicCompositor::~BasicCompositor() { MOZ_COUNT_DTOR(BasicCompositor); } bool BasicCompositor::Initialize(nsCString* const out_failureReason) { return mWidget ? mWidget->InitCompositor(this) : false; }; int32_t BasicCompositor::GetMaxTextureSize() const { return mMaxTextureSize; } void BasicCompositingRenderTarget::BindRenderTarget() { if (mClearOnBind) { mDrawTarget->ClearRect(Rect(0, 0, mSize.width, mSize.height)); mClearOnBind = false; } } void BasicCompositor::DetachWidget() { if (mWidget) { if (mIsPendingEndRemoteDrawing) { // Force to end previous remote drawing. TryToEndRemoteDrawing(/* aForceToEnd */ true); MOZ_ASSERT(!mIsPendingEndRemoteDrawing); } mWidget->CleanupRemoteDrawing(); } Compositor::DetachWidget(); } TextureFactoryIdentifier BasicCompositor::GetTextureFactoryIdentifier() { TextureFactoryIdentifier ident(LayersBackend::LAYERS_BASIC, XRE_GetProcessType(), GetMaxTextureSize()); return ident; } already_AddRefed BasicCompositor::CreateRenderTarget(const IntRect& aRect, SurfaceInitMode aInit) { MOZ_ASSERT(aRect.width != 0 && aRect.height != 0, "Trying to create a render target of invalid size"); if (aRect.width * aRect.height == 0) { return nullptr; } RefPtr target = mDrawTarget->CreateSimilarDrawTarget(aRect.Size(), SurfaceFormat::B8G8R8A8); if (!target) { return nullptr; } RefPtr rt = new BasicCompositingRenderTarget(target, aRect); return rt.forget(); } already_AddRefed BasicCompositor::CreateRenderTargetFromSource(const IntRect &aRect, const CompositingRenderTarget *aSource, const IntPoint &aSourcePoint) { MOZ_CRASH("GFX: Shouldn't be called!"); return nullptr; } already_AddRefed BasicCompositor::CreateRenderTargetForWindow(const LayoutDeviceIntRect& aRect, const LayoutDeviceIntRect& aClearRect, BufferMode aBufferMode) { MOZ_ASSERT(mDrawTarget); MOZ_ASSERT(aRect.width != 0 && aRect.height != 0, "Trying to create a render target of invalid size"); if (aRect.width * aRect.height == 0) { return nullptr; } RefPtr rt; IntRect rect = aRect.ToUnknownRect(); if (aBufferMode != BufferMode::BUFFER_NONE) { RefPtr target = mWidget->GetBackBufferDrawTarget(mDrawTarget, aRect, aClearRect); if (!target) { return nullptr; } MOZ_ASSERT(target != mDrawTarget); rt = new BasicCompositingRenderTarget(target, rect); } else { IntRect windowRect = rect; // Adjust bounds rect to account for new origin at (0, 0). if (windowRect.Size() != mDrawTarget->GetSize()) { windowRect.ExpandToEnclose(IntPoint(0, 0)); } rt = new BasicCompositingRenderTarget(mDrawTarget, windowRect); if (!aClearRect.IsEmpty()) { IntRect clearRect = aRect.ToUnknownRect(); mDrawTarget->ClearRect(Rect(clearRect - rt->GetOrigin())); } } return rt.forget(); } already_AddRefed BasicCompositor::CreateDataTextureSource(TextureFlags aFlags) { RefPtr result = new DataTextureSourceBasic(nullptr); if (aFlags & TextureFlags::RGB_FROM_YCBCR) { result->mFromYCBCR = true; } return result.forget(); } already_AddRefed BasicCompositor::CreateDataTextureSourceAround(DataSourceSurface* aSurface) { RefPtr result = new DataTextureSourceBasic(aSurface); return result.forget(); } already_AddRefed BasicCompositor::CreateDataTextureSourceAroundYCbCr(TextureHost* aTexture) { BufferTextureHost* bufferTexture = aTexture->AsBufferTextureHost(); MOZ_ASSERT(bufferTexture); if (!bufferTexture) { return nullptr; } RefPtr result = new WrappingTextureSourceYCbCrBasic(bufferTexture); return result.forget(); } bool BasicCompositor::SupportsEffect(EffectTypes aEffect) { return aEffect != EffectTypes::YCBCR && aEffect != EffectTypes::COMPONENT_ALPHA; } static void DrawSurfaceWithTextureCoords(DrawTarget *aDest, const gfx::Rect& aDestRect, SourceSurface *aSource, const gfx::Rect& aTextureCoords, gfx::SamplingFilter aSamplingFilter, const DrawOptions& aOptions, SourceSurface *aMask, const Matrix* aMaskTransform) { if (!aSource) { gfxWarning() << "DrawSurfaceWithTextureCoords problem " << gfx::hexa(aSource) << " and " << gfx::hexa(aMask); return; } // Convert aTextureCoords into aSource's coordinate space gfxRect sourceRect(aTextureCoords.x * aSource->GetSize().width, aTextureCoords.y * aSource->GetSize().height, aTextureCoords.width * aSource->GetSize().width, aTextureCoords.height * aSource->GetSize().height); // Floating point error can accumulate above and we know our visible region // is integer-aligned, so round it out. sourceRect.Round(); // Compute a transform that maps sourceRect to aDestRect. Matrix matrix = gfxUtils::TransformRectToRect(sourceRect, gfx::IntPoint::Truncate(aDestRect.x, aDestRect.y), gfx::IntPoint::Truncate(aDestRect.XMost(), aDestRect.y), gfx::IntPoint::Truncate(aDestRect.XMost(), aDestRect.YMost())); // Only use REPEAT if aTextureCoords is outside (0, 0, 1, 1). gfx::Rect unitRect(0, 0, 1, 1); ExtendMode mode = unitRect.Contains(aTextureCoords) ? ExtendMode::CLAMP : ExtendMode::REPEAT; FillRectWithMask(aDest, aDestRect, aSource, aSamplingFilter, aOptions, mode, aMask, aMaskTransform, &matrix); } static void SetupMask(const EffectChain& aEffectChain, DrawTarget* aDest, const IntPoint& aOffset, RefPtr& aMaskSurface, Matrix& aMaskTransform) { if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) { EffectMask *effectMask = static_cast(aEffectChain.mSecondaryEffects[EffectTypes::MASK].get()); aMaskSurface = effectMask->mMaskTexture->AsSourceBasic()->GetSurface(aDest); if (!aMaskSurface) { gfxWarning() << "Invalid sourceMask effect"; } MOZ_ASSERT(effectMask->mMaskTransform.Is2D(), "How did we end up with a 3D transform here?!"); aMaskTransform = effectMask->mMaskTransform.As2D(); aMaskTransform.PostTranslate(-aOffset.x, -aOffset.y); } } static bool AttemptVideoScale(TextureSourceBasic* aSource, const SourceSurface* aSourceMask, gfx::Float aOpacity, CompositionOp aBlendMode, const TexturedEffect* aTexturedEffect, const Matrix& aNewTransform, const gfx::Rect& aRect, const gfx::Rect& aClipRect, DrawTarget* aDest, const DrawTarget* aBuffer) { #ifdef MOZILLA_SSE_HAVE_CPUID_DETECTION if (!mozilla::supports_ssse3()) return false; if (aNewTransform.IsTranslation()) // unscaled painting should take the regular path return false; if (aNewTransform.HasNonAxisAlignedTransform() || aNewTransform.HasNegativeScaling()) return false; if (aSourceMask || aOpacity != 1.0f) return false; if (aBlendMode != CompositionOp::OP_OVER && aBlendMode != CompositionOp::OP_SOURCE) return false; IntRect dstRect; // the compiler should know a lot about aNewTransform at this point // maybe it can do some sophisticated optimization of the following if (!aNewTransform.TransformBounds(aRect).ToIntRect(&dstRect)) return false; IntRect clipRect; if (!aClipRect.ToIntRect(&clipRect)) return false; if (!(aTexturedEffect->mTextureCoords == Rect(0.0f, 0.0f, 1.0f, 1.0f))) return false; if (aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16) return false; uint8_t* dstData; IntSize dstSize; int32_t dstStride; SurfaceFormat dstFormat; if (aDest->LockBits(&dstData, &dstSize, &dstStride, &dstFormat)) { // If we're not painting to aBuffer the clip will // be applied later IntRect fillRect = dstRect; if (aDest == aBuffer) { // we need to clip fillRect because LockBits ignores the clip on the aDest fillRect = fillRect.Intersect(clipRect); } fillRect = fillRect.Intersect(IntRect(IntPoint(0, 0), aDest->GetSize())); IntPoint offset = fillRect.TopLeft() - dstRect.TopLeft(); RefPtr srcSource = aSource->GetSurface(aDest)->GetDataSurface(); DataSourceSurface::ScopedMap mapSrc(srcSource, DataSourceSurface::READ); bool success = ssse3_scale_data((uint32_t*)mapSrc.GetData(), srcSource->GetSize().width, srcSource->GetSize().height, mapSrc.GetStride()/4, ((uint32_t*)dstData) + fillRect.x + (dstStride / 4) * fillRect.y, dstRect.width, dstRect.height, dstStride / 4, offset.x, offset.y, fillRect.width, fillRect.height); aDest->ReleaseBits(dstData); return success; } else #endif // MOZILLA_SSE_HAVE_CPUID_DETECTION return false; } static bool AttemptVideoConvertAndScale(TextureSource* aSource, const SourceSurface* aSourceMask, gfx::Float aOpacity, CompositionOp aBlendMode, const TexturedEffect* aTexturedEffect, const Matrix& aNewTransform, const gfx::Rect& aRect, const gfx::Rect& aClipRect, DrawTarget* aDest, const DrawTarget* aBuffer) { #if defined(XP_WIN) && defined(_M_X64) // libyuv does not support SIMD scaling on win 64bit. See Bug 1295927. return false; #endif WrappingTextureSourceYCbCrBasic* wrappingSource = aSource->AsWrappingTextureSourceYCbCrBasic(); if (!wrappingSource) return false; #ifdef MOZILLA_SSE_HAVE_CPUID_DETECTION if (!mozilla::supports_ssse3()) // libyuv requests SSSE3 for fast YUV conversion. return false; if (aNewTransform.HasNonAxisAlignedTransform() || aNewTransform.HasNegativeScaling()) return false; if (aSourceMask || aOpacity != 1.0f) return false; if (aBlendMode != CompositionOp::OP_OVER && aBlendMode != CompositionOp::OP_SOURCE) return false; IntRect dstRect; // the compiler should know a lot about aNewTransform at this point // maybe it can do some sophisticated optimization of the following if (!aNewTransform.TransformBounds(aRect).ToIntRect(&dstRect)) return false; IntRect clipRect; if (!aClipRect.ToIntRect(&clipRect)) return false; if (!(aTexturedEffect->mTextureCoords == Rect(0.0f, 0.0f, 1.0f, 1.0f))) return false; if (aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16) return false; if (aDest == aBuffer && !clipRect.Contains(dstRect)) return false; if (!IntRect(IntPoint(0, 0), aDest->GetSize()).Contains(dstRect)) return false; uint8_t* dstData; IntSize dstSize; int32_t dstStride; SurfaceFormat dstFormat; if (aDest->LockBits(&dstData, &dstSize, &dstStride, &dstFormat)) { wrappingSource->ConvertAndScale(dstFormat, dstRect.Size(), dstData + ptrdiff_t(dstRect.x) * BytesPerPixel(dstFormat) + ptrdiff_t(dstRect.y) * dstStride, dstStride); aDest->ReleaseBits(dstData); return true; } else #endif // MOZILLA_SSE_HAVE_CPUID_DETECTION return false; } void BasicCompositor::DrawQuad(const gfx::Rect& aRect, const gfx::IntRect& aClipRect, const EffectChain &aEffectChain, gfx::Float aOpacity, const gfx::Matrix4x4& aTransform, const gfx::Rect& aVisibleRect) { RefPtr buffer = mRenderTarget->mDrawTarget; // For 2D drawing, |dest| and |buffer| are the same surface. For 3D drawing, // |dest| is a temporary surface. RefPtr dest = buffer; AutoRestoreTransform autoRestoreTransform(dest); Matrix newTransform; Rect transformBounds; Matrix4x4 new3DTransform; IntPoint offset = mRenderTarget->GetOrigin(); if (aTransform.Is2D()) { newTransform = aTransform.As2D(); } else { // Create a temporary surface for the transform. dest = Factory::CreateDrawTarget(gfxVars::ContentBackend(), RoundedOut(aRect).Size(), SurfaceFormat::B8G8R8A8); if (!dest) { return; } dest->SetTransform(Matrix::Translation(-aRect.x, -aRect.y)); // Get the bounds post-transform. transformBounds = aTransform.TransformAndClipBounds(aRect, Rect(offset.x, offset.y, buffer->GetSize().width, buffer->GetSize().height)); transformBounds.RoundOut(); if (transformBounds.IsEmpty()) { return; } newTransform = Matrix(); // When we apply the 3D transformation, we do it against a temporary // surface, so undo the coordinate offset. new3DTransform = aTransform; new3DTransform.PreTranslate(aRect.x, aRect.y, 0); } // XXX the transform is probably just an integer offset so this whole // business here is a bit silly. Rect transformedClipRect = buffer->GetTransform().TransformBounds(Rect(aClipRect)); buffer->PushClipRect(Rect(aClipRect)); newTransform.PostTranslate(-offset.x, -offset.y); buffer->SetTransform(newTransform); RefPtr sourceMask; Matrix maskTransform; if (aTransform.Is2D()) { SetupMask(aEffectChain, dest, offset, sourceMask, maskTransform); } CompositionOp blendMode = CompositionOp::OP_OVER; if (Effect* effect = aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE].get()) { blendMode = static_cast(effect)->mBlendMode; } switch (aEffectChain.mPrimaryEffect->mType) { case EffectTypes::SOLID_COLOR: { EffectSolidColor* effectSolidColor = static_cast(aEffectChain.mPrimaryEffect.get()); bool unboundedOp = !IsOperatorBoundByMask(blendMode); if (unboundedOp) { dest->PushClipRect(aRect); } FillRectWithMask(dest, aRect, effectSolidColor->mColor, DrawOptions(aOpacity, blendMode), sourceMask, &maskTransform); if (unboundedOp) { dest->PopClip(); } break; } case EffectTypes::RGB: { TexturedEffect* texturedEffect = static_cast(aEffectChain.mPrimaryEffect.get()); TextureSourceBasic* source = texturedEffect->mTexture->AsSourceBasic(); if (source && texturedEffect->mPremultiplied) { // we have a fast path for video here if (source->mFromYCBCR && AttemptVideoConvertAndScale(texturedEffect->mTexture, sourceMask, aOpacity, blendMode, texturedEffect, newTransform, aRect, transformedClipRect, dest, buffer)) { // we succeeded in convert and scaling } else if (source->mFromYCBCR && !source->GetSurface(dest)) { gfxWarning() << "Failed to get YCbCr to rgb surface."; } else if (source->mFromYCBCR && AttemptVideoScale(source, sourceMask, aOpacity, blendMode, texturedEffect, newTransform, aRect, transformedClipRect, dest, buffer)) { // we succeeded in scaling } else { DrawSurfaceWithTextureCoords(dest, aRect, source->GetSurface(dest), texturedEffect->mTextureCoords, texturedEffect->mSamplingFilter, DrawOptions(aOpacity, blendMode), sourceMask, &maskTransform); } } else if (source) { SourceSurface* srcSurf = source->GetSurface(dest); if (srcSurf) { RefPtr srcData = srcSurf->GetDataSurface(); // Yes, we re-create the premultiplied data every time. // This might be better with a cache, eventually. RefPtr premultData = gfxUtils::CreatePremultipliedDataSurface(srcData); DrawSurfaceWithTextureCoords(dest, aRect, premultData, texturedEffect->mTextureCoords, texturedEffect->mSamplingFilter, DrawOptions(aOpacity, blendMode), sourceMask, &maskTransform); } } else { gfxDevCrash(LogReason::IncompatibleBasicTexturedEffect) << "Bad for basic with " << texturedEffect->mTexture->Name() << " and " << gfx::hexa(sourceMask); } break; } case EffectTypes::YCBCR: { NS_RUNTIMEABORT("Can't (easily) support component alpha with BasicCompositor!"); break; } case EffectTypes::RENDER_TARGET: { EffectRenderTarget* effectRenderTarget = static_cast(aEffectChain.mPrimaryEffect.get()); RefPtr surface = static_cast(effectRenderTarget->mRenderTarget.get()); RefPtr sourceSurf = surface->mDrawTarget->Snapshot(); DrawSurfaceWithTextureCoords(dest, aRect, sourceSurf, effectRenderTarget->mTextureCoords, effectRenderTarget->mSamplingFilter, DrawOptions(aOpacity, blendMode), sourceMask, &maskTransform); break; } case EffectTypes::COMPONENT_ALPHA: { NS_RUNTIMEABORT("Can't (easily) support component alpha with BasicCompositor!"); break; } default: { NS_RUNTIMEABORT("Invalid effect type!"); break; } } if (!aTransform.Is2D()) { dest->Flush(); RefPtr destSnapshot = dest->Snapshot(); SetupMask(aEffectChain, buffer, offset, sourceMask, maskTransform); if (sourceMask) { RefPtr transformDT = dest->CreateSimilarDrawTarget(IntSize::Truncate(transformBounds.width, transformBounds.height), SurfaceFormat::B8G8R8A8); new3DTransform.PostTranslate(-transformBounds.x, -transformBounds.y, 0); if (transformDT && transformDT->Draw3DTransformedSurface(destSnapshot, new3DTransform)) { RefPtr transformSnapshot = transformDT->Snapshot(); // Transform the source by it's normal transform, and then the inverse // of the mask transform so that it's in the mask's untransformed // coordinate space. Matrix sourceTransform = newTransform; sourceTransform.PostTranslate(transformBounds.TopLeft()); Matrix inverseMask = maskTransform; inverseMask.Invert(); sourceTransform *= inverseMask; SurfacePattern source(transformSnapshot, ExtendMode::CLAMP, sourceTransform); buffer->PushClipRect(transformBounds); // Mask in the untransformed coordinate space, and then transform // by the mask transform to put the result back into destination // coords. buffer->SetTransform(maskTransform); buffer->MaskSurface(source, sourceMask, Point(0, 0)); buffer->PopClip(); } } else { buffer->Draw3DTransformedSurface(destSnapshot, new3DTransform); } } buffer->PopClip(); } void BasicCompositor::ClearRect(const gfx::Rect& aRect) { mRenderTarget->mDrawTarget->ClearRect(aRect); } void BasicCompositor::BeginFrame(const nsIntRegion& aInvalidRegion, const gfx::IntRect *aClipRectIn, const gfx::IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion, gfx::IntRect *aClipRectOut /* = nullptr */, gfx::IntRect *aRenderBoundsOut /* = nullptr */) { if (mIsPendingEndRemoteDrawing) { // Force to end previous remote drawing. TryToEndRemoteDrawing(/* aForceToEnd */ true); MOZ_ASSERT(!mIsPendingEndRemoteDrawing); } LayoutDeviceIntRect intRect(LayoutDeviceIntPoint(), mWidget->GetClientSize()); IntRect rect = IntRect(0, 0, intRect.width, intRect.height); LayoutDeviceIntRegion invalidRegionSafe; if (mDidExternalComposition) { // We do not know rendered region during external composition, just redraw // whole widget. invalidRegionSafe = intRect; mDidExternalComposition = false; } else { // Sometimes the invalid region is larger than we want to draw. invalidRegionSafe.And( LayoutDeviceIntRegion::FromUnknownRegion(aInvalidRegion), intRect); } mInvalidRegion = invalidRegionSafe; mInvalidRect = mInvalidRegion.GetBounds(); if (aRenderBoundsOut) { *aRenderBoundsOut = IntRect(); } BufferMode bufferMode = BufferMode::BUFFERED; if (mTarget) { // If we have a copy target, then we don't have a widget-provided mDrawTarget (currently). Use a dummy // placeholder so that CreateRenderTarget() works. This is only used to create a new buffered // draw target that we composite into, then copy the results the destination. mDrawTarget = mTarget; bufferMode = BufferMode::BUFFER_NONE; } else { // StartRemoteDrawingInRegion can mutate mInvalidRegion. mDrawTarget = mWidget->StartRemoteDrawingInRegion(mInvalidRegion, &bufferMode); if (!mDrawTarget) { return; } mInvalidRect = mInvalidRegion.GetBounds(); if (mInvalidRect.IsEmpty()) { mWidget->EndRemoteDrawingInRegion(mDrawTarget, mInvalidRegion); return; } } if (!mDrawTarget || mInvalidRect.IsEmpty()) { return; } LayoutDeviceIntRect clearRect; if (!aOpaqueRegion.IsEmpty()) { LayoutDeviceIntRegion clearRegion = mInvalidRegion; clearRegion.SubOut(LayoutDeviceIntRegion::FromUnknownRegion(aOpaqueRegion)); clearRect = clearRegion.GetBounds(); } else { clearRect = mInvalidRect; } // Prevent CreateRenderTargetForWindow from clearing unwanted area. gfxUtils::ClipToRegion(mDrawTarget, mInvalidRegion.ToUnknownRegion()); // Setup an intermediate render target to buffer all compositing. We will // copy this into mDrawTarget (the widget), and/or mTarget in EndFrame() RefPtr target = CreateRenderTargetForWindow(mInvalidRect, clearRect, bufferMode); mDrawTarget->PopClip(); if (!target) { if (!mTarget) { mWidget->EndRemoteDrawingInRegion(mDrawTarget, mInvalidRegion); } return; } SetRenderTarget(target); // We only allocate a surface sized to the invalidated region, so we need to // translate future coordinates. mRenderTarget->mDrawTarget->SetTransform(Matrix::Translation(-mRenderTarget->GetOrigin())); gfxUtils::ClipToRegion(mRenderTarget->mDrawTarget, mInvalidRegion.ToUnknownRegion()); if (aRenderBoundsOut) { *aRenderBoundsOut = rect; } if (aClipRectIn) { mRenderTarget->mDrawTarget->PushClipRect(Rect(*aClipRectIn)); } else { mRenderTarget->mDrawTarget->PushClipRect(Rect(rect)); if (aClipRectOut) { *aClipRectOut = rect; } } } void BasicCompositor::EndFrame() { Compositor::EndFrame(); // Pop aClipRectIn/bounds rect mRenderTarget->mDrawTarget->PopClip(); if (gfxPrefs::WidgetUpdateFlashing()) { float r = float(rand()) / RAND_MAX; float g = float(rand()) / RAND_MAX; float b = float(rand()) / RAND_MAX; // We're still clipped to mInvalidRegion, so just fill the bounds. mRenderTarget->mDrawTarget->FillRect( IntRectToRect(mInvalidRegion.GetBounds()).ToUnknownRect(), ColorPattern(Color(r, g, b, 0.2f))); } // Pop aInvalidregion mRenderTarget->mDrawTarget->PopClip(); TryToEndRemoteDrawing(); } void BasicCompositor::TryToEndRemoteDrawing(bool aForceToEnd) { if (mIsDestroyed || !mRenderTarget) { return; } // It it is not a good timing for EndRemoteDrawing, defter to call it. if (!aForceToEnd && !mTarget && NeedsToDeferEndRemoteDrawing()) { mIsPendingEndRemoteDrawing = true; const uint32_t retryMs = 2; RefPtr self = this; RefPtr runnable = NS_NewRunnableFunction([self]() { self->TryToEndRemoteDrawing(); }); MessageLoop::current()->PostDelayedTask(runnable.forget(), retryMs); return; } if (mRenderTarget->mDrawTarget != mDrawTarget) { // Note: Most platforms require us to buffer drawing to the widget surface. // That's why we don't draw to mDrawTarget directly. RefPtr source; if (mRenderTarget->mDrawTarget != mDrawTarget) { source = mWidget->EndBackBufferDrawing(); } else { source = mRenderTarget->mDrawTarget->Snapshot(); } RefPtr dest(mTarget ? mTarget : mDrawTarget); nsIntPoint offset = mTarget ? mTargetBounds.TopLeft() : nsIntPoint(); // The source DrawTarget is clipped to the invalidation region, so we have // to copy the individual rectangles in the region or else we'll draw blank // pixels. for (auto iter = mInvalidRegion.RectIter(); !iter.Done(); iter.Next()) { const LayoutDeviceIntRect& r = iter.Get(); dest->CopySurface(source, IntRect(r.x, r.y, r.width, r.height) - mRenderTarget->GetOrigin(), IntPoint(r.x, r.y) - offset); } } if (aForceToEnd || !mTarget) { mWidget->EndRemoteDrawingInRegion(mDrawTarget, mInvalidRegion); } mDrawTarget = nullptr; mRenderTarget = nullptr; mIsPendingEndRemoteDrawing = false; } bool BasicCompositor::NeedsToDeferEndRemoteDrawing() { MOZ_ASSERT(mDrawTarget); MOZ_ASSERT(mRenderTarget); if (mTarget || mRenderTarget->mDrawTarget == mDrawTarget) { return false; } return mWidget->NeedsToDeferEndRemoteDrawing(); } void BasicCompositor::FinishPendingComposite() { TryToEndRemoteDrawing(/* aForceToEnd */ true); } void BasicCompositor::EndFrameForExternalComposition(const gfx::Matrix& aTransform) { MOZ_ASSERT(!mTarget); MOZ_ASSERT(!mDrawTarget); MOZ_ASSERT(!mRenderTarget); mDidExternalComposition = true; } BasicCompositor* AssertBasicCompositor(Compositor* aCompositor) { BasicCompositor* compositor = aCompositor ? aCompositor->AsBasicCompositor() : nullptr; MOZ_DIAGNOSTIC_ASSERT(!!compositor); return compositor; } } // namespace layers } // namespace mozilla