forked from mirrors/gecko-dev
In MultiTiledContentClient we can create a DrawTargetTiled with a different origin than the layer we are painting. We must therefore ensure when edge-padding that we provide the valid region in the draw target's device-space rather than layer-space. Not doing so was causing us to pad out in incorrect directions, causing visible seams. Differential Revision: https://phabricator.services.mozilla.com/D3993 --HG-- extra : moz-landing-system : lando
679 lines
26 KiB
C++
679 lines
26 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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/MultiTiledContentClient.h"
|
|
|
|
#include "ClientTiledPaintedLayer.h"
|
|
#include "mozilla/layers/LayerMetricsWrapper.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace gfx;
|
|
|
|
namespace layers {
|
|
|
|
MultiTiledContentClient::MultiTiledContentClient(ClientTiledPaintedLayer& aPaintedLayer,
|
|
ClientLayerManager* aManager)
|
|
: TiledContentClient(aManager, "Multi")
|
|
, mTiledBuffer(aPaintedLayer, *this, aManager, &mSharedFrameMetricsHelper)
|
|
, mLowPrecisionTiledBuffer(aPaintedLayer, *this, aManager, &mSharedFrameMetricsHelper)
|
|
{
|
|
MOZ_COUNT_CTOR(MultiTiledContentClient);
|
|
mLowPrecisionTiledBuffer.SetResolution(gfxPrefs::LowPrecisionResolution());
|
|
mHasLowPrecision = gfxPrefs::UseLowPrecisionBuffer();
|
|
}
|
|
|
|
void
|
|
MultiTiledContentClient::ClearCachedResources()
|
|
{
|
|
CompositableClient::ClearCachedResources();
|
|
mTiledBuffer.DiscardBuffers();
|
|
mLowPrecisionTiledBuffer.DiscardBuffers();
|
|
}
|
|
|
|
void
|
|
MultiTiledContentClient::UpdatedBuffer(TiledBufferType aType)
|
|
{
|
|
ClientMultiTiledLayerBuffer* buffer = aType == LOW_PRECISION_TILED_BUFFER
|
|
? &mLowPrecisionTiledBuffer
|
|
: &mTiledBuffer;
|
|
|
|
MOZ_ASSERT(aType != LOW_PRECISION_TILED_BUFFER || mHasLowPrecision);
|
|
|
|
mForwarder->UseTiledLayerBuffer(this, buffer->GetSurfaceDescriptorTiles());
|
|
}
|
|
|
|
ClientMultiTiledLayerBuffer::ClientMultiTiledLayerBuffer(ClientTiledPaintedLayer& aPaintedLayer,
|
|
CompositableClient& aCompositableClient,
|
|
ClientLayerManager* aManager,
|
|
SharedFrameMetricsHelper* aHelper)
|
|
: ClientTiledLayerBuffer(aPaintedLayer, aCompositableClient)
|
|
, mManager(aManager)
|
|
, mCallback(nullptr)
|
|
, mCallbackData(nullptr)
|
|
, mSharedFrameMetricsHelper(aHelper)
|
|
{
|
|
}
|
|
|
|
void
|
|
ClientMultiTiledLayerBuffer::DiscardBuffers()
|
|
{
|
|
for (TileClient& tile : mRetainedTiles) {
|
|
tile.DiscardBuffers();
|
|
}
|
|
}
|
|
|
|
SurfaceDescriptorTiles
|
|
ClientMultiTiledLayerBuffer::GetSurfaceDescriptorTiles()
|
|
{
|
|
InfallibleTArray<TileDescriptor> tiles;
|
|
|
|
for (TileClient& tile : mRetainedTiles) {
|
|
TileDescriptor tileDesc = tile.GetTileDescriptor();
|
|
tiles.AppendElement(tileDesc);
|
|
// Reset the update rect
|
|
tile.mUpdateRect = IntRect();
|
|
}
|
|
return SurfaceDescriptorTiles(mValidRegion,
|
|
tiles,
|
|
mTileOrigin, mTileSize,
|
|
mTiles.mFirst.x, mTiles.mFirst.y,
|
|
mTiles.mSize.width, mTiles.mSize.height,
|
|
mResolution, mFrameResolution.xScale,
|
|
mFrameResolution.yScale,
|
|
mWasLastPaintProgressive);
|
|
}
|
|
|
|
void
|
|
ClientMultiTiledLayerBuffer::PaintThebes(const nsIntRegion& aNewValidRegion,
|
|
const nsIntRegion& aPaintRegion,
|
|
const nsIntRegion& aDirtyRegion,
|
|
LayerManager::DrawPaintedLayerCallback aCallback,
|
|
void* aCallbackData,
|
|
TilePaintFlags aFlags)
|
|
{
|
|
TILING_LOG("TILING %p: PaintThebes painting region %s\n", &mPaintedLayer, Stringify(aPaintRegion).c_str());
|
|
TILING_LOG("TILING %p: PaintThebes new valid region %s\n", &mPaintedLayer, Stringify(aNewValidRegion).c_str());
|
|
|
|
mCallback = aCallback;
|
|
mCallbackData = aCallbackData;
|
|
mWasLastPaintProgressive = !!(aFlags & TilePaintFlags::Progressive);
|
|
|
|
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
|
|
long start = PR_IntervalNow();
|
|
#endif
|
|
|
|
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
|
|
if (PR_IntervalNow() - start > 30) {
|
|
const IntRect bounds = aPaintRegion.GetBounds();
|
|
printf_stderr("Time to draw %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, bounds.x, bounds.y, bounds.width, bounds.height);
|
|
if (aPaintRegion.IsComplex()) {
|
|
printf_stderr("Complex region\n");
|
|
for (auto iter = aPaintRegion.RectIter(); !iter.Done(); iter.Next()) {
|
|
const IntRect& rect = iter.Get();
|
|
printf_stderr(" rect %i, %i, %i, %i\n",
|
|
rect.x, rect.y, rect.width, rect.height);
|
|
}
|
|
}
|
|
}
|
|
start = PR_IntervalNow();
|
|
#endif
|
|
|
|
AUTO_PROFILER_LABEL("ClientMultiTiledLayerBuffer::PaintThebes", GRAPHICS);
|
|
|
|
mNewValidRegion = aNewValidRegion;
|
|
Update(aNewValidRegion, aPaintRegion, aDirtyRegion, aFlags);
|
|
|
|
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
|
|
if (PR_IntervalNow() - start > 10) {
|
|
const IntRect bounds = aPaintRegion.GetBounds();
|
|
printf_stderr("Time to tile %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, bounds.x, bounds.y, bounds.width, bounds.height);
|
|
}
|
|
#endif
|
|
|
|
mLastPaintContentType = GetContentType(&mLastPaintSurfaceMode);
|
|
mCallback = nullptr;
|
|
mCallbackData = nullptr;
|
|
}
|
|
|
|
void ClientMultiTiledLayerBuffer::MaybeSyncTextures(const nsIntRegion& aPaintRegion,
|
|
const TilesPlacement& aNewTiles,
|
|
const IntSize& aScaledTileSize)
|
|
{
|
|
if (mManager->AsShadowForwarder()->SupportsTextureDirectMapping()) {
|
|
AutoTArray<uint64_t, 10> syncTextureSerials;
|
|
SurfaceMode mode;
|
|
Unused << GetContentType(&mode);
|
|
|
|
// Pre-pass through the tiles (mirroring the filter logic below) to gather
|
|
// texture IDs that we need to ensure are unused by the GPU before we
|
|
// continue.
|
|
if (!aPaintRegion.IsEmpty()) {
|
|
MOZ_ASSERT(mPaintTasks.size() == 0);
|
|
for (size_t i = 0; i < mRetainedTiles.Length(); ++i) {
|
|
const TileCoordIntPoint tileCoord = aNewTiles.TileCoord(i);
|
|
|
|
IntPoint tileOffset = GetTileOffset(tileCoord);
|
|
nsIntRegion tileDrawRegion = IntRect(tileOffset, aScaledTileSize);
|
|
tileDrawRegion.AndWith(aPaintRegion);
|
|
|
|
if (tileDrawRegion.IsEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
TileClient& tile = mRetainedTiles[i];
|
|
tile.GetSyncTextureSerials(mode, syncTextureSerials);
|
|
}
|
|
}
|
|
|
|
if (syncTextureSerials.Length() > 0) {
|
|
mManager->AsShadowForwarder()->SyncTextures(syncTextureSerials);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClientMultiTiledLayerBuffer::Update(const nsIntRegion& newValidRegion,
|
|
const nsIntRegion& aPaintRegion,
|
|
const nsIntRegion& aDirtyRegion,
|
|
TilePaintFlags aFlags)
|
|
{
|
|
const IntSize scaledTileSize = GetScaledTileSize();
|
|
const gfx::IntRect newBounds = newValidRegion.GetBounds();
|
|
|
|
const TilesPlacement oldTiles = mTiles;
|
|
const TilesPlacement newTiles(floor_div(newBounds.X(), scaledTileSize.width),
|
|
floor_div(newBounds.Y(), scaledTileSize.height),
|
|
floor_div(GetTileStart(newBounds.X(), scaledTileSize.width)
|
|
+ newBounds.Width(), scaledTileSize.width) + 1,
|
|
floor_div(GetTileStart(newBounds.Y(), scaledTileSize.height)
|
|
+ newBounds.Height(), scaledTileSize.height) + 1);
|
|
|
|
const size_t oldTileCount = mRetainedTiles.Length();
|
|
const size_t newTileCount = newTiles.mSize.width * newTiles.mSize.height;
|
|
|
|
nsTArray<TileClient> oldRetainedTiles;
|
|
mRetainedTiles.SwapElements(oldRetainedTiles);
|
|
mRetainedTiles.SetLength(newTileCount);
|
|
|
|
for (size_t oldIndex = 0; oldIndex < oldTileCount; oldIndex++) {
|
|
const TileCoordIntPoint tileCoord = oldTiles.TileCoord(oldIndex);
|
|
const size_t newIndex = newTiles.TileIndex(tileCoord);
|
|
// First, get the already existing tiles to the right place in the new array.
|
|
// Leave placeholders (default constructor) where there was no tile.
|
|
if (newTiles.HasTile(tileCoord)) {
|
|
mRetainedTiles[newIndex] = oldRetainedTiles[oldIndex];
|
|
} else {
|
|
// release tiles that we are not going to reuse before allocating new ones
|
|
// to avoid allocating unnecessarily.
|
|
oldRetainedTiles[oldIndex].DiscardBuffers();
|
|
}
|
|
}
|
|
|
|
oldRetainedTiles.Clear();
|
|
|
|
nsIntRegion paintRegion = aPaintRegion;
|
|
nsIntRegion dirtyRegion = aDirtyRegion;
|
|
|
|
MaybeSyncTextures(paintRegion, newTiles, scaledTileSize);
|
|
|
|
if (!paintRegion.IsEmpty()) {
|
|
MOZ_ASSERT(mPaintTasks.size() == 0);
|
|
|
|
for (size_t i = 0; i < newTileCount; ++i) {
|
|
const TileCoordIntPoint tileCoord = newTiles.TileCoord(i);
|
|
|
|
IntPoint tileOffset = GetTileOffset(tileCoord);
|
|
nsIntRegion tileDrawRegion = IntRect(tileOffset, scaledTileSize);
|
|
tileDrawRegion.AndWith(paintRegion);
|
|
|
|
if (tileDrawRegion.IsEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
TileClient& tile = mRetainedTiles[i];
|
|
if (!ValidateTile(tile, GetTileOffset(tileCoord), tileDrawRegion, aFlags)) {
|
|
gfxCriticalError() << "ValidateTile failed";
|
|
}
|
|
|
|
// Validating the tile may have required more to be painted.
|
|
paintRegion.OrWith(tileDrawRegion);
|
|
dirtyRegion.OrWith(tileDrawRegion);
|
|
}
|
|
|
|
if (!mPaintTiles.empty()) {
|
|
// Create a tiled draw target
|
|
gfx::TileSet tileset;
|
|
for (size_t i = 0; i < mPaintTiles.size(); ++i) {
|
|
mPaintTiles[i].mTileOrigin -= mTilingOrigin;
|
|
}
|
|
tileset.mTiles = &mPaintTiles[0];
|
|
tileset.mTileCount = mPaintTiles.size();
|
|
RefPtr<DrawTarget> drawTarget = gfx::Factory::CreateTiledDrawTarget(tileset);
|
|
if (!drawTarget || !drawTarget->IsValid()) {
|
|
gfxDevCrash(LogReason::InvalidContext) << "Invalid tiled draw target";
|
|
return;
|
|
}
|
|
drawTarget->SetTransform(Matrix());
|
|
|
|
// Draw into the tiled draw target
|
|
RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(drawTarget);
|
|
MOZ_ASSERT(ctx); // already checked the draw target above
|
|
ctx->SetMatrix(
|
|
ctx->CurrentMatrix().PreScale(mResolution, mResolution).PreTranslate(-mTilingOrigin));
|
|
|
|
mCallback(&mPaintedLayer, ctx, paintRegion, dirtyRegion,
|
|
DrawRegionClip::DRAW, nsIntRegion(), mCallbackData);
|
|
ctx = nullptr;
|
|
|
|
// Edge padding allows us to avoid resampling artifacts
|
|
if (gfxPrefs::TileEdgePaddingEnabled() && mResolution == 1) {
|
|
drawTarget->PadEdges(newValidRegion.MovedBy(-mTilingOrigin));
|
|
}
|
|
|
|
// Reset
|
|
mPaintTiles.clear();
|
|
mTilingOrigin = IntPoint(std::numeric_limits<int32_t>::max(),
|
|
std::numeric_limits<int32_t>::max());
|
|
}
|
|
|
|
// Dispatch to the paint thread
|
|
if (aFlags & TilePaintFlags::Async) {
|
|
bool queuedTask = false;
|
|
|
|
for (const auto& state : mPaintTasks) {
|
|
if (!state->mCapture->IsEmpty()) {
|
|
PaintThread::Get()->QueuePaintTask(state);
|
|
queuedTask = true;
|
|
}
|
|
}
|
|
|
|
if (queuedTask) {
|
|
mManager->SetQueuedAsyncPaints();
|
|
}
|
|
|
|
mPaintTasks.clear();
|
|
}
|
|
|
|
for (uint32_t i = 0; i < mRetainedTiles.Length(); ++i) {
|
|
TileClient& tile = mRetainedTiles[i];
|
|
UnlockTile(tile);
|
|
}
|
|
}
|
|
|
|
mTiles = newTiles;
|
|
mValidRegion = newValidRegion;
|
|
}
|
|
|
|
bool
|
|
ClientMultiTiledLayerBuffer::ValidateTile(TileClient& aTile,
|
|
const nsIntPoint& aTileOrigin,
|
|
nsIntRegion& aDirtyRegion,
|
|
TilePaintFlags aFlags)
|
|
{
|
|
AUTO_PROFILER_LABEL("ClientMultiTiledLayerBuffer::ValidateTile", GRAPHICS);
|
|
|
|
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
|
|
if (aDirtyRegion.IsComplex()) {
|
|
printf_stderr("Complex region\n");
|
|
}
|
|
#endif
|
|
|
|
SurfaceMode mode;
|
|
gfxContentType content = GetContentType(&mode);
|
|
|
|
if (!aTile.mAllocator) {
|
|
aTile.SetTextureAllocator(mManager->GetCompositorBridgeChild()->GetTexturePool(
|
|
mManager->AsShadowForwarder(),
|
|
gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content),
|
|
TextureFlags::DISALLOW_BIGIMAGE | TextureFlags::IMMEDIATE_UPLOAD | TextureFlags::NON_BLOCKING_READ_LOCK));
|
|
MOZ_ASSERT(aTile.mAllocator);
|
|
}
|
|
|
|
nsIntRegion tileDirtyRegion = aDirtyRegion.MovedBy(-aTileOrigin);
|
|
tileDirtyRegion.ScaleRoundOut(mResolution, mResolution);
|
|
|
|
nsIntRegion tileVisibleRegion = mNewValidRegion.MovedBy(-aTileOrigin);
|
|
tileVisibleRegion.ScaleRoundOut(mResolution, mResolution);
|
|
|
|
std::vector<RefPtr<TextureClient>> asyncPaintClients;
|
|
|
|
Maybe<AcquiredBackBuffer> backBuffer =
|
|
aTile.AcquireBackBuffer(mCompositableClient,
|
|
tileDirtyRegion,
|
|
tileVisibleRegion,
|
|
content,
|
|
mode,
|
|
aFlags);
|
|
|
|
if (!backBuffer) {
|
|
return false;
|
|
}
|
|
|
|
// Mark the area we need to paint in the back buffer as invalid in the
|
|
// front buffer as they will become out of sync.
|
|
aTile.mInvalidFront.OrWith(tileDirtyRegion);
|
|
|
|
// Add the backbuffer's invalid region intersected with the visible region to the
|
|
// dirty region we will be painting. This will be empty if we are able to copy
|
|
// from the front into the back.
|
|
nsIntRegion tileInvalidRegion = aTile.mInvalidBack;
|
|
tileInvalidRegion.AndWith(tileVisibleRegion);
|
|
|
|
nsIntRegion invalidRegion = tileInvalidRegion;
|
|
invalidRegion.MoveBy(aTileOrigin);
|
|
invalidRegion.ScaleInverseRoundOut(mResolution, mResolution);
|
|
|
|
tileDirtyRegion.OrWith(tileInvalidRegion);
|
|
aDirtyRegion.OrWith(invalidRegion);
|
|
|
|
// Mark the region we will be painting and the region we copied from the front buffer as
|
|
// needing to be uploaded to the compositor
|
|
aTile.mUpdateRect = tileDirtyRegion.GetBounds().Union(backBuffer->mUpdatedRect);
|
|
|
|
// We need to clear the dirty region of the tile before painting
|
|
// if we are painting non-opaque content
|
|
if (mode != SurfaceMode::SURFACE_OPAQUE) {
|
|
for (auto iter = tileDirtyRegion.RectIter(); !iter.Done(); iter.Next()) {
|
|
const gfx::Rect drawRect(iter.Get().X(), iter.Get().Y(),
|
|
iter.Get().Width(), iter.Get().Height());
|
|
backBuffer->mTarget->ClearRect(drawRect);
|
|
}
|
|
}
|
|
|
|
gfx::Tile paintTile;
|
|
paintTile.mTileOrigin = gfx::IntPoint(aTileOrigin.x, aTileOrigin.y);
|
|
paintTile.mDrawTarget = backBuffer->mTarget;
|
|
mPaintTiles.push_back(paintTile);
|
|
|
|
if (aFlags & TilePaintFlags::Async) {
|
|
RefPtr<PaintTask> task = new PaintTask();
|
|
task->mCapture = backBuffer->mCapture;
|
|
task->mTarget = backBuffer->mBackBuffer;
|
|
task->mClients = std::move(backBuffer->mTextureClients);
|
|
mPaintTasks.push_back(task);
|
|
} else {
|
|
MOZ_RELEASE_ASSERT(backBuffer->mTarget == backBuffer->mBackBuffer);
|
|
MOZ_RELEASE_ASSERT(backBuffer->mCapture == nullptr);
|
|
}
|
|
|
|
mTilingOrigin.x = std::min(mTilingOrigin.x, paintTile.mTileOrigin.x);
|
|
mTilingOrigin.y = std::min(mTilingOrigin.y, paintTile.mTileOrigin.y);
|
|
|
|
// The new buffer is now validated, remove the dirty region from it.
|
|
aTile.mInvalidBack.SubOut(tileDirtyRegion);
|
|
|
|
aTile.Flip();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This function takes the transform stored in aTransformToCompBounds
|
|
* (which was generated in GetTransformToAncestorsParentLayer), and
|
|
* modifies it with the ViewTransform from the compositor side so that
|
|
* it reflects what the compositor is actually rendering. This operation
|
|
* basically adds in the layer's async transform.
|
|
* This function then returns the scroll ancestor's composition bounds,
|
|
* transformed into the painted layer's LayerPixel coordinates, accounting
|
|
* for the compositor state.
|
|
*/
|
|
static Maybe<LayerRect>
|
|
GetCompositorSideCompositionBounds(const LayerMetricsWrapper& aScrollAncestor,
|
|
const LayerToParentLayerMatrix4x4& aTransformToCompBounds,
|
|
const AsyncTransform& aAPZTransform,
|
|
const LayerRect& aClip)
|
|
{
|
|
LayerToParentLayerMatrix4x4 transform = aTransformToCompBounds *
|
|
AsyncTransformComponentMatrix(aAPZTransform);
|
|
|
|
return UntransformBy(transform.Inverse(),
|
|
aScrollAncestor.Metrics().GetCompositionBounds(), aClip);
|
|
}
|
|
|
|
bool
|
|
ClientMultiTiledLayerBuffer::ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion,
|
|
const nsIntRegion& aOldValidRegion,
|
|
nsIntRegion& aRegionToPaint,
|
|
BasicTiledLayerPaintData* aPaintData,
|
|
bool aIsRepeated)
|
|
{
|
|
aRegionToPaint = aInvalidRegion;
|
|
|
|
// If the composition bounds rect is empty, we can't make any sensible
|
|
// decision about how to update coherently. In this case, just update
|
|
// everything in one transaction.
|
|
if (aPaintData->mCompositionBounds.IsEmpty()) {
|
|
aPaintData->mPaintFinished = true;
|
|
return false;
|
|
}
|
|
|
|
// If this is a low precision buffer, we force progressive updates. The
|
|
// assumption is that the contents is less important, so visual coherency
|
|
// is lower priority than speed.
|
|
bool drawingLowPrecision = IsLowPrecision();
|
|
|
|
// Find out if we have any non-stale content to update.
|
|
nsIntRegion staleRegion;
|
|
staleRegion.And(aInvalidRegion, aOldValidRegion);
|
|
|
|
TILING_LOG("TILING %p: Progressive update stale region %s\n", &mPaintedLayer, Stringify(staleRegion).c_str());
|
|
|
|
LayerMetricsWrapper scrollAncestor;
|
|
mPaintedLayer.GetAncestorLayers(&scrollAncestor, nullptr, nullptr);
|
|
|
|
// Find out the current view transform to determine which tiles to draw
|
|
// first, and see if we should just abort this paint. Aborting is usually
|
|
// caused by there being an incoming, more relevant paint.
|
|
AsyncTransform viewTransform;
|
|
MOZ_ASSERT(mSharedFrameMetricsHelper);
|
|
|
|
bool abortPaint =
|
|
mSharedFrameMetricsHelper->UpdateFromCompositorFrameMetrics(
|
|
scrollAncestor,
|
|
!staleRegion.Contains(aInvalidRegion),
|
|
drawingLowPrecision,
|
|
viewTransform);
|
|
|
|
TILING_LOG("TILING %p: Progressive update view transform %s zoom %f abort %d\n",
|
|
&mPaintedLayer, ToString(viewTransform.mTranslation).c_str(), viewTransform.mScale.scale, abortPaint);
|
|
|
|
if (abortPaint) {
|
|
// We ignore if front-end wants to abort if this is the first,
|
|
// non-low-precision paint, as in that situation, we're about to override
|
|
// front-end's page/viewport metrics.
|
|
if (!aPaintData->mFirstPaint || drawingLowPrecision) {
|
|
AUTO_PROFILER_LABEL(
|
|
"ClientMultiTiledLayerBuffer::ComputeProgressiveUpdateRegion",
|
|
GRAPHICS);
|
|
|
|
aRegionToPaint.SetEmpty();
|
|
return aIsRepeated;
|
|
}
|
|
}
|
|
|
|
Maybe<LayerRect> transformedCompositionBounds =
|
|
GetCompositorSideCompositionBounds(scrollAncestor,
|
|
aPaintData->mTransformToCompBounds,
|
|
viewTransform,
|
|
LayerRect(mPaintedLayer.GetVisibleRegion().GetBounds()));
|
|
|
|
if (!transformedCompositionBounds) {
|
|
aPaintData->mPaintFinished = true;
|
|
return false;
|
|
}
|
|
|
|
TILING_LOG("TILING %p: Progressive update transformed compositor bounds %s\n", &mPaintedLayer, Stringify(*transformedCompositionBounds).c_str());
|
|
|
|
// Compute a "coherent update rect" that we should paint all at once in a
|
|
// single transaction. This is to avoid rendering glitches on animated
|
|
// page content, and when layers change size/shape.
|
|
// On Fennec uploads are more expensive because we're not using gralloc, so
|
|
// we use a coherent update rect that is intersected with the screen at the
|
|
// time of issuing the draw command. This will paint faster but also potentially
|
|
// make the progressive paint more visible to the user while scrolling.
|
|
IntRect coherentUpdateRect(RoundedOut(
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
transformedCompositionBounds->Intersect(aPaintData->mCompositionBounds)
|
|
#else
|
|
*transformedCompositionBounds
|
|
#endif
|
|
).ToUnknownRect());
|
|
|
|
TILING_LOG("TILING %p: Progressive update final coherency rect %s\n", &mPaintedLayer, Stringify(coherentUpdateRect).c_str());
|
|
|
|
aRegionToPaint.And(aInvalidRegion, coherentUpdateRect);
|
|
aRegionToPaint.Or(aRegionToPaint, staleRegion);
|
|
bool drawingStale = !aRegionToPaint.IsEmpty();
|
|
if (!drawingStale) {
|
|
aRegionToPaint = aInvalidRegion;
|
|
}
|
|
|
|
// Prioritise tiles that are currently visible on the screen.
|
|
bool paintingVisible = false;
|
|
if (aRegionToPaint.Intersects(coherentUpdateRect)) {
|
|
aRegionToPaint.And(aRegionToPaint, coherentUpdateRect);
|
|
paintingVisible = true;
|
|
}
|
|
|
|
TILING_LOG("TILING %p: Progressive update final paint region %s\n", &mPaintedLayer, Stringify(aRegionToPaint).c_str());
|
|
|
|
// Paint area that's visible and overlaps previously valid content to avoid
|
|
// visible glitches in animated elements, such as gifs.
|
|
bool paintInSingleTransaction = paintingVisible && (drawingStale || aPaintData->mFirstPaint);
|
|
|
|
TILING_LOG("TILING %p: paintingVisible %d drawingStale %d firstPaint %d singleTransaction %d\n",
|
|
&mPaintedLayer, paintingVisible, drawingStale, aPaintData->mFirstPaint, paintInSingleTransaction);
|
|
|
|
// The following code decides what order to draw tiles in, based on the
|
|
// current scroll direction of the primary scrollable layer.
|
|
NS_ASSERTION(!aRegionToPaint.IsEmpty(), "Unexpectedly empty paint region!");
|
|
IntRect paintBounds = aRegionToPaint.GetBounds();
|
|
|
|
int startX, incX, startY, incY;
|
|
gfx::IntSize scaledTileSize = GetScaledTileSize();
|
|
if (aPaintData->mScrollOffset.x >= aPaintData->mLastScrollOffset.x) {
|
|
startX = RoundDownToTileEdge(paintBounds.X(), scaledTileSize.width);
|
|
incX = scaledTileSize.width;
|
|
} else {
|
|
startX = RoundDownToTileEdge(paintBounds.XMost() - 1, scaledTileSize.width);
|
|
incX = -scaledTileSize.width;
|
|
}
|
|
|
|
if (aPaintData->mScrollOffset.y >= aPaintData->mLastScrollOffset.y) {
|
|
startY = RoundDownToTileEdge(paintBounds.Y(), scaledTileSize.height);
|
|
incY = scaledTileSize.height;
|
|
} else {
|
|
startY = RoundDownToTileEdge(paintBounds.YMost() - 1, scaledTileSize.height);
|
|
incY = -scaledTileSize.height;
|
|
}
|
|
|
|
// Find a tile to draw.
|
|
IntRect tileBounds(startX, startY, scaledTileSize.width, scaledTileSize.height);
|
|
int32_t scrollDiffX = aPaintData->mScrollOffset.x - aPaintData->mLastScrollOffset.x;
|
|
int32_t scrollDiffY = aPaintData->mScrollOffset.y - aPaintData->mLastScrollOffset.y;
|
|
// This loop will always terminate, as there is at least one tile area
|
|
// along the first/last row/column intersecting with regionToPaint, or its
|
|
// bounds would have been smaller.
|
|
while (true) {
|
|
aRegionToPaint.And(aInvalidRegion, tileBounds);
|
|
if (!aRegionToPaint.IsEmpty()) {
|
|
if (mResolution != 1) {
|
|
// Paint the entire tile for low-res. This is aimed to fixing low-res resampling
|
|
// and to avoid doing costly region accurate painting for a small area.
|
|
aRegionToPaint = tileBounds;
|
|
}
|
|
break;
|
|
}
|
|
if (Abs(scrollDiffY) >= Abs(scrollDiffX)) {
|
|
tileBounds.MoveByX(incX);
|
|
} else {
|
|
tileBounds.MoveByY(incY);
|
|
}
|
|
}
|
|
|
|
if (!aRegionToPaint.Contains(aInvalidRegion)) {
|
|
// The region needed to paint is larger then our progressive chunk size
|
|
// therefore update what we want to paint and ask for a new paint transaction.
|
|
|
|
// If we need to draw more than one tile to maintain coherency, make
|
|
// sure it happens in the same transaction by requesting this work be
|
|
// repeated immediately.
|
|
// If this is unnecessary, the remaining work will be done tile-by-tile in
|
|
// subsequent transactions. The caller code is responsible for scheduling
|
|
// the subsequent transactions as long as we don't set the mPaintFinished
|
|
// flag to true.
|
|
return (!drawingLowPrecision && paintInSingleTransaction);
|
|
}
|
|
|
|
// We're not repeating painting and we've not requested a repeat transaction,
|
|
// so the paint is finished. If there's still a separate low precision
|
|
// paint to do, it will get marked as unfinished later.
|
|
aPaintData->mPaintFinished = true;
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
ClientMultiTiledLayerBuffer::ProgressiveUpdate(const nsIntRegion& aValidRegion,
|
|
const nsIntRegion& aInvalidRegion,
|
|
const nsIntRegion& aOldValidRegion,
|
|
nsIntRegion& aOutDrawnRegion,
|
|
BasicTiledLayerPaintData* aPaintData,
|
|
LayerManager::DrawPaintedLayerCallback aCallback,
|
|
void* aCallbackData)
|
|
{
|
|
TILING_LOG("TILING %p: Progressive update valid region %s\n", &mPaintedLayer, Stringify(aValidRegion).c_str());
|
|
TILING_LOG("TILING %p: Progressive update invalid region %s\n", &mPaintedLayer, Stringify(aInvalidRegion).c_str());
|
|
TILING_LOG("TILING %p: Progressive update old valid region %s\n", &mPaintedLayer, Stringify(aOldValidRegion).c_str());
|
|
|
|
bool repeat = false;
|
|
bool isBufferChanged = false;
|
|
nsIntRegion remainingInvalidRegion = aInvalidRegion;
|
|
nsIntRegion updatedValidRegion = aValidRegion;
|
|
do {
|
|
// Compute the region that should be updated. Repeat as many times as
|
|
// is required.
|
|
nsIntRegion regionToPaint;
|
|
repeat = ComputeProgressiveUpdateRegion(remainingInvalidRegion,
|
|
aOldValidRegion,
|
|
regionToPaint,
|
|
aPaintData,
|
|
repeat);
|
|
|
|
TILING_LOG("TILING %p: Progressive update computed paint region %s repeat %d\n", &mPaintedLayer, Stringify(regionToPaint).c_str(), repeat);
|
|
|
|
// There's no further work to be done.
|
|
if (regionToPaint.IsEmpty()) {
|
|
break;
|
|
}
|
|
|
|
isBufferChanged = true;
|
|
|
|
// Keep track of what we're about to refresh.
|
|
aOutDrawnRegion.OrWith(regionToPaint);
|
|
updatedValidRegion.OrWith(regionToPaint);
|
|
|
|
// aValidRegion may have been altered by InvalidateRegion, but we still
|
|
// want to display stale content until it gets progressively updated.
|
|
// Create a region that includes stale content.
|
|
nsIntRegion validOrStale;
|
|
validOrStale.Or(updatedValidRegion, aOldValidRegion);
|
|
|
|
// Paint the computed region and subtract it from the invalid region.
|
|
PaintThebes(validOrStale, regionToPaint, remainingInvalidRegion,
|
|
aCallback, aCallbackData, TilePaintFlags::Progressive);
|
|
remainingInvalidRegion.SubOut(regionToPaint);
|
|
} while (repeat);
|
|
|
|
TILING_LOG("TILING %p: Progressive update final valid region %s buffer changed %d\n", &mPaintedLayer, Stringify(updatedValidRegion).c_str(), isBufferChanged);
|
|
TILING_LOG("TILING %p: Progressive update final invalid region %s\n", &mPaintedLayer, Stringify(remainingInvalidRegion).c_str());
|
|
|
|
// Return false if nothing has been drawn, or give what has been drawn
|
|
// to the shadow layer to upload.
|
|
return isBufferChanged;
|
|
}
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|