michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "RotatedBuffer.h" michael@0: #include // for int32_t michael@0: #include // for max michael@0: #include "BasicImplData.h" // for BasicImplData michael@0: #include "BasicLayersImpl.h" // for ToData michael@0: #include "BufferUnrotate.h" // for BufferUnrotate michael@0: #include "GeckoProfiler.h" // for PROFILER_LABEL michael@0: #include "Layers.h" // for ThebesLayer, Layer, etc michael@0: #include "gfxPlatform.h" // for gfxPlatform michael@0: #include "gfxPrefs.h" // for gfxPrefs michael@0: #include "gfxUtils.h" // for gfxUtils michael@0: #include "mozilla/ArrayUtils.h" // for ArrayLength michael@0: #include "mozilla/gfx/BasePoint.h" // for BasePoint michael@0: #include "mozilla/gfx/BaseRect.h" // for BaseRect michael@0: #include "mozilla/gfx/BaseSize.h" // for BaseSize michael@0: #include "mozilla/gfx/Matrix.h" // for Matrix michael@0: #include "mozilla/gfx/Point.h" // for Point, IntPoint michael@0: #include "mozilla/gfx/Rect.h" // for Rect, IntRect michael@0: #include "mozilla/gfx/Types.h" // for ExtendMode::ExtendMode::CLAMP, etc michael@0: #include "mozilla/layers/ShadowLayers.h" // for ShadowableLayer michael@0: #include "mozilla/layers/TextureClient.h" // for TextureClient michael@0: #include "nsSize.h" // for nsIntSize michael@0: #include "gfx2DGlue.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace gfx; michael@0: michael@0: namespace layers { michael@0: michael@0: nsIntRect michael@0: RotatedBuffer::GetQuadrantRectangle(XSide aXSide, YSide aYSide) const michael@0: { michael@0: // quadrantTranslation is the amount we translate the top-left michael@0: // of the quadrant by to get coordinates relative to the layer michael@0: nsIntPoint quadrantTranslation = -mBufferRotation; michael@0: quadrantTranslation.x += aXSide == LEFT ? mBufferRect.width : 0; michael@0: quadrantTranslation.y += aYSide == TOP ? mBufferRect.height : 0; michael@0: return mBufferRect + quadrantTranslation; michael@0: } michael@0: michael@0: Rect michael@0: RotatedBuffer::GetSourceRectangle(XSide aXSide, YSide aYSide) const michael@0: { michael@0: Rect result; michael@0: if (aXSide == LEFT) { michael@0: result.x = 0; michael@0: result.width = mBufferRotation.x; michael@0: } else { michael@0: result.x = mBufferRotation.x; michael@0: result.width = mBufferRect.width - mBufferRotation.x; michael@0: } michael@0: if (aYSide == TOP) { michael@0: result.y = 0; michael@0: result.height = mBufferRotation.y; michael@0: } else { michael@0: result.y = mBufferRotation.y; michael@0: result.height = mBufferRect.height - mBufferRotation.y; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: /** michael@0: * @param aXSide LEFT means we draw from the left side of the buffer (which michael@0: * is drawn on the right side of mBufferRect). RIGHT means we draw from michael@0: * the right side of the buffer (which is drawn on the left side of michael@0: * mBufferRect). michael@0: * @param aYSide TOP means we draw from the top side of the buffer (which michael@0: * is drawn on the bottom side of mBufferRect). BOTTOM means we draw from michael@0: * the bottom side of the buffer (which is drawn on the top side of michael@0: * mBufferRect). michael@0: */ michael@0: void michael@0: RotatedBuffer::DrawBufferQuadrant(gfx::DrawTarget* aTarget, michael@0: XSide aXSide, YSide aYSide, michael@0: ContextSource aSource, michael@0: float aOpacity, michael@0: gfx::CompositionOp aOperator, michael@0: gfx::SourceSurface* aMask, michael@0: const gfx::Matrix* aMaskTransform) const michael@0: { michael@0: // The rectangle that we're going to fill. Basically we're going to michael@0: // render the buffer at mBufferRect + quadrantTranslation to get the michael@0: // pixels in the right place, but we're only going to paint within michael@0: // mBufferRect michael@0: nsIntRect quadrantRect = GetQuadrantRectangle(aXSide, aYSide); michael@0: nsIntRect fillRect; michael@0: if (!fillRect.IntersectRect(mBufferRect, quadrantRect)) michael@0: return; michael@0: michael@0: gfx::Point quadrantTranslation(quadrantRect.x, quadrantRect.y); michael@0: michael@0: MOZ_ASSERT(aOperator == CompositionOp::OP_OVER || aOperator == CompositionOp::OP_SOURCE); michael@0: // direct2d is much slower when using OP_SOURCE so use OP_OVER and michael@0: // (maybe) a clear instead. Normally we need to draw in a single operation michael@0: // (to avoid flickering) but direct2d is ok since it defers rendering. michael@0: // We should try abstract this logic in a helper when we have other use michael@0: // cases. michael@0: if (aTarget->GetType() == BackendType::DIRECT2D && aOperator == CompositionOp::OP_SOURCE) { michael@0: aOperator = CompositionOp::OP_OVER; michael@0: if (mDTBuffer->GetFormat() == SurfaceFormat::B8G8R8A8) { michael@0: aTarget->ClearRect(ToRect(fillRect)); michael@0: } michael@0: } michael@0: michael@0: RefPtr snapshot; michael@0: if (aSource == BUFFER_BLACK) { michael@0: snapshot = mDTBuffer->Snapshot(); michael@0: } else { michael@0: MOZ_ASSERT(aSource == BUFFER_WHITE); michael@0: snapshot = mDTBufferOnWhite->Snapshot(); michael@0: } michael@0: michael@0: if (aOperator == CompositionOp::OP_SOURCE) { michael@0: // OP_SOURCE is unbounded in Azure, and we really don't want that behaviour here. michael@0: // We also can't do a ClearRect+FillRect since we need the drawing to happen michael@0: // as an atomic operation (to prevent flickering). michael@0: aTarget->PushClipRect(gfx::Rect(fillRect.x, fillRect.y, michael@0: fillRect.width, fillRect.height)); michael@0: } michael@0: michael@0: if (aMask) { michael@0: Matrix oldTransform = aTarget->GetTransform(); michael@0: michael@0: // Transform from user -> buffer space. michael@0: Matrix transform; michael@0: transform.Translate(quadrantTranslation.x, quadrantTranslation.y); michael@0: michael@0: Matrix inverseMask = *aMaskTransform; michael@0: inverseMask.Invert(); michael@0: michael@0: transform *= oldTransform; michael@0: transform *= inverseMask; michael@0: michael@0: #ifdef MOZ_GFX_OPTIMIZE_MOBILE michael@0: SurfacePattern source(snapshot, ExtendMode::CLAMP, transform, Filter::POINT); michael@0: #else michael@0: SurfacePattern source(snapshot, ExtendMode::CLAMP, transform); michael@0: #endif michael@0: michael@0: aTarget->SetTransform(*aMaskTransform); michael@0: aTarget->MaskSurface(source, aMask, Point(0, 0), DrawOptions(aOpacity, aOperator)); michael@0: aTarget->SetTransform(oldTransform); michael@0: } else { michael@0: #ifdef MOZ_GFX_OPTIMIZE_MOBILE michael@0: DrawSurfaceOptions options(Filter::POINT); michael@0: #else michael@0: DrawSurfaceOptions options; michael@0: #endif michael@0: aTarget->DrawSurface(snapshot, ToRect(fillRect), michael@0: GetSourceRectangle(aXSide, aYSide), michael@0: options, michael@0: DrawOptions(aOpacity, aOperator)); michael@0: } michael@0: michael@0: if (aOperator == CompositionOp::OP_SOURCE) { michael@0: aTarget->PopClip(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: RotatedBuffer::DrawBufferWithRotation(gfx::DrawTarget *aTarget, ContextSource aSource, michael@0: float aOpacity, michael@0: gfx::CompositionOp aOperator, michael@0: gfx::SourceSurface* aMask, michael@0: const gfx::Matrix* aMaskTransform) const michael@0: { michael@0: PROFILER_LABEL("RotatedBuffer", "DrawBufferWithRotation"); michael@0: // See above, in Azure Repeat should always be a safe, even faster choice michael@0: // though! Particularly on D2D Repeat should be a lot faster, need to look michael@0: // into that. TODO[Bas] michael@0: DrawBufferQuadrant(aTarget, LEFT, TOP, aSource, aOpacity, aOperator, aMask, aMaskTransform); michael@0: DrawBufferQuadrant(aTarget, RIGHT, TOP, aSource, aOpacity, aOperator, aMask, aMaskTransform); michael@0: DrawBufferQuadrant(aTarget, LEFT, BOTTOM, aSource, aOpacity, aOperator, aMask, aMaskTransform); michael@0: DrawBufferQuadrant(aTarget, RIGHT, BOTTOM, aSource, aOpacity, aOperator,aMask, aMaskTransform); michael@0: } michael@0: michael@0: /* static */ bool michael@0: RotatedContentBuffer::IsClippingCheap(DrawTarget* aTarget, const nsIntRegion& aRegion) michael@0: { michael@0: // Assume clipping is cheap if the draw target just has an integer michael@0: // translation, and the visible region is simple. michael@0: return !aTarget->GetTransform().HasNonIntegerTranslation() && michael@0: aRegion.GetNumRects() <= 1; michael@0: } michael@0: michael@0: void michael@0: RotatedContentBuffer::DrawTo(ThebesLayer* aLayer, michael@0: DrawTarget* aTarget, michael@0: float aOpacity, michael@0: CompositionOp aOp, michael@0: SourceSurface* aMask, michael@0: const Matrix* aMaskTransform) michael@0: { michael@0: if (!EnsureBuffer()) { michael@0: return; michael@0: } michael@0: michael@0: bool clipped = false; michael@0: michael@0: // If the entire buffer is valid, we can just draw the whole thing, michael@0: // no need to clip. But we'll still clip if clipping is cheap --- michael@0: // that might let us copy a smaller region of the buffer. michael@0: // Also clip to the visible region if we're told to. michael@0: if (!aLayer->GetValidRegion().Contains(BufferRect()) || michael@0: (ToData(aLayer)->GetClipToVisibleRegion() && michael@0: !aLayer->GetVisibleRegion().Contains(BufferRect())) || michael@0: IsClippingCheap(aTarget, aLayer->GetEffectiveVisibleRegion())) { michael@0: // We don't want to draw invalid stuff, so we need to clip. Might as michael@0: // well clip to the smallest area possible --- the visible region. michael@0: // Bug 599189 if there is a non-integer-translation transform in aTarget, michael@0: // we might sample pixels outside GetEffectiveVisibleRegion(), which is wrong michael@0: // and may cause gray lines. michael@0: gfxUtils::ClipToRegionSnapped(aTarget, aLayer->GetEffectiveVisibleRegion()); michael@0: clipped = true; michael@0: } michael@0: michael@0: DrawBufferWithRotation(aTarget, BUFFER_BLACK, aOpacity, aOp, aMask, aMaskTransform); michael@0: if (clipped) { michael@0: aTarget->PopClip(); michael@0: } michael@0: } michael@0: michael@0: DrawTarget* michael@0: RotatedContentBuffer::BorrowDrawTargetForQuadrantUpdate(const nsIntRect& aBounds, michael@0: ContextSource aSource, michael@0: DrawIterator* aIter) michael@0: { michael@0: nsIntRect bounds = aBounds; michael@0: if (aIter) { michael@0: // If an iterator was provided, then BeginPaint must have been run with michael@0: // PAINT_CAN_DRAW_ROTATED, and the draw region might cover multiple quadrants. michael@0: // Iterate over each of them, and return an appropriate buffer each time we find michael@0: // one that intersects the draw region. The iterator mCount value tracks which michael@0: // quadrants we have considered across multiple calls to this function. michael@0: aIter->mDrawRegion.SetEmpty(); michael@0: while (aIter->mCount < 4) { michael@0: nsIntRect quadrant = GetQuadrantRectangle((aIter->mCount & 1) ? LEFT : RIGHT, michael@0: (aIter->mCount & 2) ? TOP : BOTTOM); michael@0: aIter->mDrawRegion.And(aBounds, quadrant); michael@0: aIter->mCount++; michael@0: if (!aIter->mDrawRegion.IsEmpty()) { michael@0: break; michael@0: } michael@0: } michael@0: if (aIter->mDrawRegion.IsEmpty()) { michael@0: return nullptr; michael@0: } michael@0: bounds = aIter->mDrawRegion.GetBounds(); michael@0: } michael@0: michael@0: if (!EnsureBuffer()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: MOZ_ASSERT(!mLoanedDrawTarget, "draw target has been borrowed and not returned"); michael@0: if (aSource == BUFFER_BOTH && HaveBufferOnWhite()) { michael@0: if (!EnsureBufferOnWhite()) { michael@0: return nullptr; michael@0: } michael@0: MOZ_ASSERT(mDTBuffer && mDTBufferOnWhite); michael@0: mLoanedDrawTarget = Factory::CreateDualDrawTarget(mDTBuffer, mDTBufferOnWhite); michael@0: } else if (aSource == BUFFER_WHITE) { michael@0: if (!EnsureBufferOnWhite()) { michael@0: return nullptr; michael@0: } michael@0: mLoanedDrawTarget = mDTBufferOnWhite; michael@0: } else { michael@0: // BUFFER_BLACK, or BUFFER_BOTH with a single buffer. michael@0: mLoanedDrawTarget = mDTBuffer; michael@0: } michael@0: michael@0: // Figure out which quadrant to draw in michael@0: int32_t xBoundary = mBufferRect.XMost() - mBufferRotation.x; michael@0: int32_t yBoundary = mBufferRect.YMost() - mBufferRotation.y; michael@0: XSide sideX = bounds.XMost() <= xBoundary ? RIGHT : LEFT; michael@0: YSide sideY = bounds.YMost() <= yBoundary ? BOTTOM : TOP; michael@0: nsIntRect quadrantRect = GetQuadrantRectangle(sideX, sideY); michael@0: NS_ASSERTION(quadrantRect.Contains(bounds), "Messed up quadrants"); michael@0: michael@0: mLoanedTransform = mLoanedDrawTarget->GetTransform(); michael@0: mLoanedTransform.Translate(-quadrantRect.x, -quadrantRect.y); michael@0: mLoanedDrawTarget->SetTransform(mLoanedTransform); michael@0: mLoanedTransform.Translate(quadrantRect.x, quadrantRect.y); michael@0: michael@0: return mLoanedDrawTarget; michael@0: } michael@0: michael@0: void michael@0: BorrowDrawTarget::ReturnDrawTarget(gfx::DrawTarget*& aReturned) michael@0: { michael@0: MOZ_ASSERT(aReturned == mLoanedDrawTarget); michael@0: mLoanedDrawTarget->SetTransform(mLoanedTransform); michael@0: mLoanedDrawTarget = nullptr; michael@0: aReturned = nullptr; michael@0: } michael@0: michael@0: gfxContentType michael@0: RotatedContentBuffer::BufferContentType() michael@0: { michael@0: if (mBufferProvider || mDTBuffer) { michael@0: SurfaceFormat format; michael@0: michael@0: if (mBufferProvider) { michael@0: format = mBufferProvider->GetFormat(); michael@0: } else if (mDTBuffer) { michael@0: format = mDTBuffer->GetFormat(); michael@0: } michael@0: michael@0: return ContentForFormat(format); michael@0: } michael@0: return gfxContentType::SENTINEL; michael@0: } michael@0: michael@0: bool michael@0: RotatedContentBuffer::BufferSizeOkFor(const nsIntSize& aSize) michael@0: { michael@0: return (aSize == mBufferRect.Size() || michael@0: (SizedToVisibleBounds != mBufferSizePolicy && michael@0: aSize < mBufferRect.Size())); michael@0: } michael@0: michael@0: bool michael@0: RotatedContentBuffer::EnsureBuffer() michael@0: { michael@0: NS_ASSERTION(!mLoanedDrawTarget, "Loaned draw target must be returned"); michael@0: if (!mDTBuffer) { michael@0: if (mBufferProvider) { michael@0: mDTBuffer = mBufferProvider->GetAsDrawTarget(); michael@0: } michael@0: } michael@0: michael@0: NS_WARN_IF_FALSE(mDTBuffer, "no buffer"); michael@0: return !!mDTBuffer; michael@0: } michael@0: michael@0: bool michael@0: RotatedContentBuffer::EnsureBufferOnWhite() michael@0: { michael@0: NS_ASSERTION(!mLoanedDrawTarget, "Loaned draw target must be returned"); michael@0: if (!mDTBufferOnWhite) { michael@0: if (mBufferProviderOnWhite) { michael@0: mDTBufferOnWhite = michael@0: mBufferProviderOnWhite->GetAsDrawTarget(); michael@0: } michael@0: } michael@0: michael@0: NS_WARN_IF_FALSE(mDTBufferOnWhite, "no buffer"); michael@0: return mDTBufferOnWhite; michael@0: } michael@0: michael@0: bool michael@0: RotatedContentBuffer::HaveBuffer() const michael@0: { michael@0: return mDTBuffer || mBufferProvider; michael@0: } michael@0: michael@0: bool michael@0: RotatedContentBuffer::HaveBufferOnWhite() const michael@0: { michael@0: return mDTBufferOnWhite || mBufferProviderOnWhite; michael@0: } michael@0: michael@0: static void michael@0: WrapRotationAxis(int32_t* aRotationPoint, int32_t aSize) michael@0: { michael@0: if (*aRotationPoint < 0) { michael@0: *aRotationPoint += aSize; michael@0: } else if (*aRotationPoint >= aSize) { michael@0: *aRotationPoint -= aSize; michael@0: } michael@0: } michael@0: michael@0: static nsIntRect michael@0: ComputeBufferRect(const nsIntRect& aRequestedRect) michael@0: { michael@0: nsIntRect rect(aRequestedRect); michael@0: // Set a minimum width to guarantee a minimum size of buffers we michael@0: // allocate (and work around problems on some platforms with smaller michael@0: // dimensions). 64 is the magic number needed to work around the michael@0: // rendering glitch, and guarantees image rows can be SIMD'd for michael@0: // even r5g6b5 surfaces pretty much everywhere. michael@0: rect.width = std::max(aRequestedRect.width, 64); michael@0: #ifdef MOZ_WIDGET_GONK michael@0: // Set a minumum height to guarantee a minumum height of buffers we michael@0: // allocate. Some GL implementations fail to render gralloc textures michael@0: // with a height 9px-16px. It happens on Adreno 200. Adreno 320 does not michael@0: // have this problem. 32 is choosed as alignment of gralloc buffers. michael@0: // See Bug 873937. michael@0: // Increase the height only when the requested height is more than 0. michael@0: // See Bug 895976. michael@0: // XXX it might be better to disable it on the gpu that does not have michael@0: // the height problem. michael@0: if (rect.height > 0) { michael@0: rect.height = std::max(aRequestedRect.height, 32); michael@0: } michael@0: #endif michael@0: return rect; michael@0: } michael@0: michael@0: void michael@0: RotatedContentBuffer::FlushBuffers() michael@0: { michael@0: if (mDTBuffer) { michael@0: mDTBuffer->Flush(); michael@0: } michael@0: if (mDTBufferOnWhite) { michael@0: mDTBufferOnWhite->Flush(); michael@0: } michael@0: } michael@0: michael@0: RotatedContentBuffer::PaintState michael@0: RotatedContentBuffer::BeginPaint(ThebesLayer* aLayer, michael@0: uint32_t aFlags) michael@0: { michael@0: PaintState result; michael@0: // We need to disable rotation if we're going to be resampled when michael@0: // drawing, because we might sample across the rotation boundary. michael@0: bool canHaveRotation = gfxPlatform::BufferRotationEnabled() && michael@0: !(aFlags & (PAINT_WILL_RESAMPLE | PAINT_NO_ROTATION)); michael@0: michael@0: nsIntRegion validRegion = aLayer->GetValidRegion(); michael@0: michael@0: bool canUseOpaqueSurface = aLayer->CanUseOpaqueSurface(); michael@0: ContentType layerContentType = michael@0: canUseOpaqueSurface ? gfxContentType::COLOR : michael@0: gfxContentType::COLOR_ALPHA; michael@0: michael@0: SurfaceMode mode; michael@0: nsIntRegion neededRegion; michael@0: bool canReuseBuffer; michael@0: nsIntRect destBufferRect; michael@0: michael@0: while (true) { michael@0: mode = aLayer->GetSurfaceMode(); michael@0: neededRegion = aLayer->GetVisibleRegion(); michael@0: canReuseBuffer = HaveBuffer() && BufferSizeOkFor(neededRegion.GetBounds().Size()); michael@0: result.mContentType = layerContentType; michael@0: michael@0: if (canReuseBuffer) { michael@0: if (mBufferRect.Contains(neededRegion.GetBounds())) { michael@0: // We don't need to adjust mBufferRect. michael@0: destBufferRect = mBufferRect; michael@0: } else if (neededRegion.GetBounds().Size() <= mBufferRect.Size()) { michael@0: // The buffer's big enough but doesn't contain everything that's michael@0: // going to be visible. We'll move it. michael@0: destBufferRect = nsIntRect(neededRegion.GetBounds().TopLeft(), mBufferRect.Size()); michael@0: } else { michael@0: destBufferRect = neededRegion.GetBounds(); michael@0: } michael@0: } else { michael@0: // We won't be reusing the buffer. Compute a new rect. michael@0: destBufferRect = ComputeBufferRect(neededRegion.GetBounds()); michael@0: } michael@0: michael@0: if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { michael@0: #if defined(MOZ_GFX_OPTIMIZE_MOBILE) || defined(MOZ_WIDGET_GONK) michael@0: mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; michael@0: #else michael@0: if (!aLayer->GetParent() || michael@0: !aLayer->GetParent()->SupportsComponentAlphaChildren() || michael@0: !aLayer->Manager()->IsCompositingCheap() || michael@0: !aLayer->AsShadowableLayer() || michael@0: !aLayer->AsShadowableLayer()->HasShadow() || michael@0: !gfxPrefs::ComponentAlphaEnabled()) { michael@0: mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; michael@0: } else { michael@0: result.mContentType = gfxContentType::COLOR; michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: if ((aFlags & PAINT_WILL_RESAMPLE) && michael@0: (!neededRegion.GetBounds().IsEqualInterior(destBufferRect) || michael@0: neededRegion.GetNumRects() > 1)) { michael@0: // The area we add to neededRegion might not be painted opaquely michael@0: if (mode == SurfaceMode::SURFACE_OPAQUE) { michael@0: result.mContentType = gfxContentType::COLOR_ALPHA; michael@0: mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; michael@0: } michael@0: michael@0: // We need to validate the entire buffer, to make sure that only valid michael@0: // pixels are sampled michael@0: neededRegion = destBufferRect; michael@0: } michael@0: michael@0: // If we have an existing buffer, but the content type has changed or we michael@0: // have transitioned into/out of component alpha, then we need to recreate it. michael@0: if (HaveBuffer() && michael@0: (result.mContentType != BufferContentType() || michael@0: (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != HaveBufferOnWhite())) { michael@0: michael@0: // We're effectively clearing the valid region, so we need to draw michael@0: // the entire needed region now. michael@0: result.mRegionToInvalidate = aLayer->GetValidRegion(); michael@0: validRegion.SetEmpty(); michael@0: Clear(); michael@0: // Restart decision process with the cleared buffer. We can only go michael@0: // around the loop one more iteration, since mDTBuffer is null now. michael@0: continue; michael@0: } michael@0: michael@0: break; michael@0: } michael@0: michael@0: NS_ASSERTION(destBufferRect.Contains(neededRegion.GetBounds()), michael@0: "Destination rect doesn't contain what we need to paint"); michael@0: michael@0: result.mRegionToDraw.Sub(neededRegion, validRegion); michael@0: michael@0: if (result.mRegionToDraw.IsEmpty()) michael@0: return result; michael@0: michael@0: // Do not modify result.mRegionToDraw or result.mContentType after this call. michael@0: // Do not modify mBufferRect, mBufferRotation, or mDidSelfCopy, michael@0: // or call CreateBuffer before this call. michael@0: FinalizeFrame(result.mRegionToDraw); michael@0: michael@0: nsIntRect drawBounds = result.mRegionToDraw.GetBounds(); michael@0: RefPtr destDTBuffer; michael@0: RefPtr destDTBufferOnWhite; michael@0: uint32_t bufferFlags = canHaveRotation ? ALLOW_REPEAT : 0; michael@0: if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { michael@0: bufferFlags |= BUFFER_COMPONENT_ALPHA; michael@0: } michael@0: if (canReuseBuffer) { michael@0: if (!EnsureBuffer()) { michael@0: return result; michael@0: } michael@0: nsIntRect keepArea; michael@0: if (keepArea.IntersectRect(destBufferRect, mBufferRect)) { michael@0: // Set mBufferRotation so that the pixels currently in mDTBuffer michael@0: // will still be rendered in the right place when mBufferRect michael@0: // changes to destBufferRect. michael@0: nsIntPoint newRotation = mBufferRotation + michael@0: (destBufferRect.TopLeft() - mBufferRect.TopLeft()); michael@0: WrapRotationAxis(&newRotation.x, mBufferRect.width); michael@0: WrapRotationAxis(&newRotation.y, mBufferRect.height); michael@0: NS_ASSERTION(nsIntRect(nsIntPoint(0,0), mBufferRect.Size()).Contains(newRotation), michael@0: "newRotation out of bounds"); michael@0: int32_t xBoundary = destBufferRect.XMost() - newRotation.x; michael@0: int32_t yBoundary = destBufferRect.YMost() - newRotation.y; michael@0: bool drawWrapsBuffer = (drawBounds.x < xBoundary && xBoundary < drawBounds.XMost()) || michael@0: (drawBounds.y < yBoundary && yBoundary < drawBounds.YMost()); michael@0: if ((drawWrapsBuffer && !(aFlags & PAINT_CAN_DRAW_ROTATED)) || michael@0: (newRotation != nsIntPoint(0,0) && !canHaveRotation)) { michael@0: // The stuff we need to redraw will wrap around an edge of the michael@0: // buffer (and the caller doesn't know how to support that), so michael@0: // move the pixels we can keep into a position that lets us michael@0: // redraw in just one quadrant. michael@0: if (mBufferRotation == nsIntPoint(0,0)) { michael@0: nsIntRect srcRect(nsIntPoint(0, 0), mBufferRect.Size()); michael@0: nsIntPoint dest = mBufferRect.TopLeft() - destBufferRect.TopLeft(); michael@0: MOZ_ASSERT(mDTBuffer); michael@0: mDTBuffer->CopyRect(IntRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height), michael@0: IntPoint(dest.x, dest.y)); michael@0: if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { michael@0: if (!EnsureBufferOnWhite()) { michael@0: return result; michael@0: } michael@0: MOZ_ASSERT(mDTBufferOnWhite); michael@0: mDTBufferOnWhite->CopyRect(IntRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height), michael@0: IntPoint(dest.x, dest.y)); michael@0: } michael@0: result.mDidSelfCopy = true; michael@0: mDidSelfCopy = true; michael@0: // Don't set destBuffer; we special-case self-copies, and michael@0: // just did the necessary work above. michael@0: mBufferRect = destBufferRect; michael@0: } else { michael@0: // With azure and a data surface perform an buffer unrotate michael@0: // (SelfCopy). michael@0: unsigned char* data; michael@0: IntSize size; michael@0: int32_t stride; michael@0: SurfaceFormat format; michael@0: michael@0: if (mDTBuffer->LockBits(&data, &size, &stride, &format)) { michael@0: uint8_t bytesPerPixel = BytesPerPixel(format); michael@0: BufferUnrotate(data, michael@0: size.width * bytesPerPixel, michael@0: size.height, stride, michael@0: newRotation.x * bytesPerPixel, newRotation.y); michael@0: mDTBuffer->ReleaseBits(data); michael@0: michael@0: if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { michael@0: if (!EnsureBufferOnWhite()) { michael@0: return result; michael@0: } michael@0: MOZ_ASSERT(mDTBufferOnWhite); michael@0: mDTBufferOnWhite->LockBits(&data, &size, &stride, &format); michael@0: uint8_t bytesPerPixel = BytesPerPixel(format); michael@0: BufferUnrotate(data, michael@0: size.width * bytesPerPixel, michael@0: size.height, stride, michael@0: newRotation.x * bytesPerPixel, newRotation.y); michael@0: mDTBufferOnWhite->ReleaseBits(data); michael@0: } michael@0: michael@0: // Buffer unrotate moves all the pixels, note that michael@0: // we self copied for SyncBackToFrontBuffer michael@0: result.mDidSelfCopy = true; michael@0: mDidSelfCopy = true; michael@0: mBufferRect = destBufferRect; michael@0: mBufferRotation = nsIntPoint(0, 0); michael@0: } michael@0: michael@0: if (!result.mDidSelfCopy) { michael@0: destBufferRect = ComputeBufferRect(neededRegion.GetBounds()); michael@0: CreateBuffer(result.mContentType, destBufferRect, bufferFlags, michael@0: &destDTBuffer, &destDTBufferOnWhite); michael@0: if (!destDTBuffer) { michael@0: return result; michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: mBufferRect = destBufferRect; michael@0: mBufferRotation = newRotation; michael@0: } michael@0: } else { michael@0: // No pixels are going to be kept. The whole visible region michael@0: // will be redrawn, so we don't need to copy anything, so we don't michael@0: // set destBuffer. michael@0: mBufferRect = destBufferRect; michael@0: mBufferRotation = nsIntPoint(0,0); michael@0: } michael@0: } else { michael@0: // The buffer's not big enough, so allocate a new one michael@0: CreateBuffer(result.mContentType, destBufferRect, bufferFlags, michael@0: &destDTBuffer, &destDTBufferOnWhite); michael@0: if (!destDTBuffer) { michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(!(aFlags & PAINT_WILL_RESAMPLE) || destBufferRect == neededRegion.GetBounds(), michael@0: "If we're resampling, we need to validate the entire buffer"); michael@0: michael@0: // If we have no buffered data already, then destBuffer will be a fresh buffer michael@0: // and we do not need to clear it below. michael@0: bool isClear = !HaveBuffer(); michael@0: michael@0: if (destDTBuffer) { michael@0: if (!isClear && (mode != SurfaceMode::SURFACE_COMPONENT_ALPHA || HaveBufferOnWhite())) { michael@0: // Copy the bits michael@0: nsIntPoint offset = -destBufferRect.TopLeft(); michael@0: Matrix mat; michael@0: mat.Translate(offset.x, offset.y); michael@0: destDTBuffer->SetTransform(mat); michael@0: if (!EnsureBuffer()) { michael@0: return result; michael@0: } michael@0: MOZ_ASSERT(mDTBuffer, "Have we got a Thebes buffer for some reason?"); michael@0: DrawBufferWithRotation(destDTBuffer, BUFFER_BLACK, 1.0, CompositionOp::OP_SOURCE); michael@0: destDTBuffer->SetTransform(Matrix()); michael@0: michael@0: if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { michael@0: NS_ASSERTION(destDTBufferOnWhite, "Must have a white buffer!"); michael@0: destDTBufferOnWhite->SetTransform(mat); michael@0: if (!EnsureBufferOnWhite()) { michael@0: return result; michael@0: } michael@0: MOZ_ASSERT(mDTBufferOnWhite, "Have we got a Thebes buffer for some reason?"); michael@0: DrawBufferWithRotation(destDTBufferOnWhite, BUFFER_WHITE, 1.0, CompositionOp::OP_SOURCE); michael@0: destDTBufferOnWhite->SetTransform(Matrix()); michael@0: } michael@0: } michael@0: michael@0: mDTBuffer = destDTBuffer.forget(); michael@0: mDTBufferOnWhite = destDTBufferOnWhite.forget(); michael@0: mBufferRect = destBufferRect; michael@0: mBufferRotation = nsIntPoint(0,0); michael@0: } michael@0: NS_ASSERTION(canHaveRotation || mBufferRotation == nsIntPoint(0,0), michael@0: "Rotation disabled, but we have nonzero rotation?"); michael@0: michael@0: nsIntRegion invalidate; michael@0: invalidate.Sub(aLayer->GetValidRegion(), destBufferRect); michael@0: result.mRegionToInvalidate.Or(result.mRegionToInvalidate, invalidate); michael@0: result.mClip = DrawRegionClip::DRAW_SNAPPED; michael@0: result.mMode = mode; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: DrawTarget* michael@0: RotatedContentBuffer::BorrowDrawTargetForPainting(const PaintState& aPaintState, michael@0: DrawIterator* aIter /* = nullptr */) michael@0: { michael@0: if (aPaintState.mMode == SurfaceMode::SURFACE_NONE) { michael@0: return nullptr; michael@0: } michael@0: michael@0: DrawTarget* result = BorrowDrawTargetForQuadrantUpdate(aPaintState.mRegionToDraw.GetBounds(), michael@0: BUFFER_BOTH, aIter); michael@0: if (!result) { michael@0: return nullptr; michael@0: } michael@0: const nsIntRegion* drawPtr = &aPaintState.mRegionToDraw; michael@0: if (aIter) { michael@0: // The iterators draw region currently only contains the bounds of the region, michael@0: // this makes it the precise region. michael@0: aIter->mDrawRegion.And(aIter->mDrawRegion, aPaintState.mRegionToDraw); michael@0: drawPtr = &aIter->mDrawRegion; michael@0: } michael@0: michael@0: if (aPaintState.mMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { michael@0: MOZ_ASSERT(mDTBuffer && mDTBufferOnWhite); michael@0: nsIntRegionRectIterator iter(*drawPtr); michael@0: const nsIntRect *iterRect; michael@0: while ((iterRect = iter.Next())) { michael@0: mDTBuffer->FillRect(Rect(iterRect->x, iterRect->y, iterRect->width, iterRect->height), michael@0: ColorPattern(Color(0.0, 0.0, 0.0, 1.0))); michael@0: mDTBufferOnWhite->FillRect(Rect(iterRect->x, iterRect->y, iterRect->width, iterRect->height), michael@0: ColorPattern(Color(1.0, 1.0, 1.0, 1.0))); michael@0: } michael@0: } else if (aPaintState.mContentType == gfxContentType::COLOR_ALPHA && HaveBuffer()) { michael@0: // HaveBuffer() => we have an existing buffer that we must clear michael@0: nsIntRegionRectIterator iter(*drawPtr); michael@0: const nsIntRect *iterRect; michael@0: while ((iterRect = iter.Next())) { michael@0: result->ClearRect(Rect(iterRect->x, iterRect->y, iterRect->width, iterRect->height)); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: } michael@0: } michael@0: