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