зеркало из https://github.com/mozilla/gecko-dev.git
1445 строки
52 KiB
C++
1445 строки
52 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 "mozilla/layers/TiledContentClient.h"
|
|
#include <math.h> // for ceil, ceilf, floor
|
|
#include <algorithm>
|
|
#include "ClientTiledPaintedLayer.h" // for ClientTiledPaintedLayer
|
|
#include "GeckoProfiler.h" // for PROFILER_LABEL
|
|
#include "ClientLayerManager.h" // for ClientLayerManager
|
|
#include "gfxContext.h" // for gfxContext, etc
|
|
#include "gfxPlatform.h" // for gfxPlatform
|
|
#include "gfxPrefs.h" // for gfxPrefs
|
|
#include "gfxRect.h" // for gfxRect
|
|
#include "mozilla/MathAlgorithms.h" // for Abs
|
|
#include "mozilla/gfx/Point.h" // for IntSize
|
|
#include "mozilla/gfx/Rect.h" // for Rect
|
|
#include "mozilla/gfx/Tools.h" // for BytesPerPixel
|
|
#include "mozilla/layers/CompositableForwarder.h"
|
|
#include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild
|
|
#include "mozilla/layers/LayerMetricsWrapper.h"
|
|
#include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder
|
|
#include "TextureClientPool.h"
|
|
#include "nsDebug.h" // for NS_ASSERTION
|
|
#include "nsISupportsImpl.h" // for gfxContext::AddRef, etc
|
|
#include "nsExpirationTracker.h" // for nsExpirationTracker
|
|
#include "nsMathUtils.h" // for NS_lroundf
|
|
#include "LayersLogging.h"
|
|
#include "UnitTransforms.h" // for TransformTo
|
|
#include "mozilla/UniquePtr.h"
|
|
|
|
// This is the minimum area that we deem reasonable to copy from the front buffer to the
|
|
// back buffer on tile updates. If the valid region is smaller than this, we just
|
|
// redraw it and save on the copy (and requisite surface-locking involved).
|
|
#define MINIMUM_TILE_COPY_AREA (1.f/16.f)
|
|
|
|
#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
|
|
#include "cairo.h"
|
|
#include <sstream>
|
|
using mozilla::layers::Layer;
|
|
static void DrawDebugOverlay(mozilla::gfx::DrawTarget* dt, int x, int y, int width, int height)
|
|
{
|
|
gfxContext c(dt);
|
|
|
|
// Draw border
|
|
c.NewPath();
|
|
c.SetDeviceColor(Color(0.f, 0.f, 0.f));
|
|
c.Rectangle(gfxRect(0, 0, width, height));
|
|
c.Stroke();
|
|
|
|
// Build tile description
|
|
std::stringstream ss;
|
|
ss << x << ", " << y;
|
|
|
|
// Draw text using cairo toy text API
|
|
// XXX: this drawing will silently fail if |dt| doesn't have a Cairo backend
|
|
cairo_t* cr = gfxFont::RefCairo(dt);
|
|
cairo_set_font_size(cr, 25);
|
|
cairo_text_extents_t extents;
|
|
cairo_text_extents(cr, ss.str().c_str(), &extents);
|
|
|
|
int textWidth = extents.width + 6;
|
|
|
|
c.NewPath();
|
|
c.SetDeviceColor(Color(0.f, 0.f, 0.f));
|
|
c.Rectangle(gfxRect(gfxPoint(2,2),gfxSize(textWidth, 30)));
|
|
c.Fill();
|
|
|
|
c.NewPath();
|
|
c.SetDeviceColor(Color(1.0, 0.0, 0.0));
|
|
c.Rectangle(gfxRect(gfxPoint(2,2),gfxSize(textWidth, 30)));
|
|
c.Stroke();
|
|
|
|
c.NewPath();
|
|
cairo_move_to(cr, 4, 28);
|
|
cairo_show_text(cr, ss.str().c_str());
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace gfx;
|
|
|
|
namespace layers {
|
|
|
|
|
|
MultiTiledContentClient::MultiTiledContentClient(ClientTiledPaintedLayer* aPaintedLayer,
|
|
ClientLayerManager* aManager)
|
|
: TiledContentClient(aManager, "Multi")
|
|
{
|
|
MOZ_COUNT_CTOR(MultiTiledContentClient);
|
|
|
|
mTiledBuffer = ClientMultiTiledLayerBuffer(aPaintedLayer, this, aManager,
|
|
&mSharedFrameMetricsHelper);
|
|
mLowPrecisionTiledBuffer = ClientMultiTiledLayerBuffer(aPaintedLayer, this, aManager,
|
|
&mSharedFrameMetricsHelper);
|
|
|
|
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());
|
|
buffer->ClearPaintedRegion();
|
|
}
|
|
|
|
SharedFrameMetricsHelper::SharedFrameMetricsHelper()
|
|
: mLastProgressiveUpdateWasLowPrecision(false)
|
|
, mProgressiveUpdateWasInDanger(false)
|
|
{
|
|
MOZ_COUNT_CTOR(SharedFrameMetricsHelper);
|
|
}
|
|
|
|
SharedFrameMetricsHelper::~SharedFrameMetricsHelper()
|
|
{
|
|
MOZ_COUNT_DTOR(SharedFrameMetricsHelper);
|
|
}
|
|
|
|
static inline bool
|
|
FuzzyEquals(float a, float b) {
|
|
return (fabsf(a - b) < 1e-6);
|
|
}
|
|
|
|
static AsyncTransform
|
|
ComputeViewTransform(const FrameMetrics& aContentMetrics, const FrameMetrics& aCompositorMetrics)
|
|
{
|
|
// This is basically the same code as AsyncPanZoomController::GetCurrentAsyncTransform
|
|
// but with aContentMetrics used in place of mLastContentPaintMetrics, because they
|
|
// should be equivalent, modulo race conditions while transactions are inflight.
|
|
|
|
ParentLayerPoint translation = (aCompositorMetrics.GetScrollOffset() - aContentMetrics.GetScrollOffset())
|
|
* aCompositorMetrics.GetZoom();
|
|
return AsyncTransform(aCompositorMetrics.GetAsyncZoom(), -translation);
|
|
}
|
|
|
|
bool
|
|
SharedFrameMetricsHelper::UpdateFromCompositorFrameMetrics(
|
|
const LayerMetricsWrapper& aLayer,
|
|
bool aHasPendingNewThebesContent,
|
|
bool aLowPrecision,
|
|
AsyncTransform& aViewTransform)
|
|
{
|
|
MOZ_ASSERT(aLayer);
|
|
|
|
CompositorBridgeChild* compositor = nullptr;
|
|
if (aLayer.Manager() &&
|
|
aLayer.Manager()->AsClientLayerManager()) {
|
|
compositor = aLayer.Manager()->AsClientLayerManager()->GetCompositorBridgeChild();
|
|
}
|
|
|
|
if (!compositor) {
|
|
return false;
|
|
}
|
|
|
|
const FrameMetrics& contentMetrics = aLayer.Metrics();
|
|
FrameMetrics compositorMetrics;
|
|
|
|
if (!compositor->LookupCompositorFrameMetrics(contentMetrics.GetScrollId(),
|
|
compositorMetrics)) {
|
|
return false;
|
|
}
|
|
|
|
aViewTransform = ComputeViewTransform(contentMetrics, compositorMetrics);
|
|
|
|
// Reset the checkerboard risk flag when switching to low precision
|
|
// rendering.
|
|
if (aLowPrecision && !mLastProgressiveUpdateWasLowPrecision) {
|
|
// Skip low precision rendering until we're at risk of checkerboarding.
|
|
if (!mProgressiveUpdateWasInDanger) {
|
|
TILING_LOG("TILING: Aborting low-precision rendering because not at risk of checkerboarding\n");
|
|
return true;
|
|
}
|
|
mProgressiveUpdateWasInDanger = false;
|
|
}
|
|
mLastProgressiveUpdateWasLowPrecision = aLowPrecision;
|
|
|
|
// Always abort updates if the resolution has changed. There's no use
|
|
// in drawing at the incorrect resolution.
|
|
if (!FuzzyEquals(compositorMetrics.GetZoom().xScale, contentMetrics.GetZoom().xScale) ||
|
|
!FuzzyEquals(compositorMetrics.GetZoom().yScale, contentMetrics.GetZoom().yScale)) {
|
|
TILING_LOG("TILING: Aborting because resolution changed from %s to %s\n",
|
|
ToString(contentMetrics.GetZoom()).c_str(), ToString(compositorMetrics.GetZoom()).c_str());
|
|
return true;
|
|
}
|
|
|
|
// Never abort drawing if we can't be sure we've sent a more recent
|
|
// display-port. If we abort updating when we shouldn't, we can end up
|
|
// with blank regions on the screen and we open up the risk of entering
|
|
// an endless updating cycle.
|
|
if (fabsf(contentMetrics.GetScrollOffset().x - compositorMetrics.GetScrollOffset().x) <= 2 &&
|
|
fabsf(contentMetrics.GetScrollOffset().y - compositorMetrics.GetScrollOffset().y) <= 2 &&
|
|
fabsf(contentMetrics.GetDisplayPort().x - compositorMetrics.GetDisplayPort().x) <= 2 &&
|
|
fabsf(contentMetrics.GetDisplayPort().y - compositorMetrics.GetDisplayPort().y) <= 2 &&
|
|
fabsf(contentMetrics.GetDisplayPort().width - compositorMetrics.GetDisplayPort().width) <= 2 &&
|
|
fabsf(contentMetrics.GetDisplayPort().height - compositorMetrics.GetDisplayPort().height) <= 2) {
|
|
return false;
|
|
}
|
|
|
|
// When not a low precision pass and the page is in danger of checker boarding
|
|
// abort update.
|
|
if (!aLowPrecision && !mProgressiveUpdateWasInDanger) {
|
|
bool scrollUpdatePending = contentMetrics.GetScrollOffsetUpdated() &&
|
|
contentMetrics.GetScrollGeneration() != compositorMetrics.GetScrollGeneration();
|
|
// If scrollUpdatePending is true, then that means the content-side
|
|
// metrics has a new scroll offset that is going to be forced into the
|
|
// compositor but it hasn't gotten there yet.
|
|
// Even though right now comparing the metrics might indicate we're
|
|
// about to checkerboard (and that's true), the checkerboarding will
|
|
// disappear as soon as the new scroll offset update is processed
|
|
// on the compositor side. To avoid leaving things in a low-precision
|
|
// paint, we need to detect and handle this case (bug 1026756).
|
|
if (!scrollUpdatePending && AboutToCheckerboard(contentMetrics, compositorMetrics)) {
|
|
mProgressiveUpdateWasInDanger = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Abort drawing stale low-precision content if there's a more recent
|
|
// display-port in the pipeline.
|
|
if (aLowPrecision && !aHasPendingNewThebesContent) {
|
|
TILING_LOG("TILING: Aborting low-precision because of new pending content\n");
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
SharedFrameMetricsHelper::AboutToCheckerboard(const FrameMetrics& aContentMetrics,
|
|
const FrameMetrics& aCompositorMetrics)
|
|
{
|
|
// The size of the painted area is originally computed in layer pixels in layout, but then
|
|
// converted to app units and then back to CSS pixels before being put in the FrameMetrics.
|
|
// This process can introduce some rounding error, so we inflate the rect by one app unit
|
|
// to account for that.
|
|
CSSRect painted = (aContentMetrics.GetCriticalDisplayPort().IsEmpty()
|
|
? aContentMetrics.GetDisplayPort()
|
|
: aContentMetrics.GetCriticalDisplayPort())
|
|
+ aContentMetrics.GetScrollOffset();
|
|
painted.Inflate(CSSMargin::FromAppUnits(nsMargin(1, 1, 1, 1)));
|
|
|
|
// Inflate the rect by the danger zone. See the description of the danger zone prefs
|
|
// in AsyncPanZoomController.cpp for an explanation of this.
|
|
CSSRect showing = CSSRect(aCompositorMetrics.GetScrollOffset(),
|
|
aCompositorMetrics.CalculateBoundedCompositedSizeInCssPixels());
|
|
showing.Inflate(LayerSize(gfxPrefs::APZDangerZoneX(), gfxPrefs::APZDangerZoneY())
|
|
/ aCompositorMetrics.LayersPixelsPerCSSPixel());
|
|
|
|
// Clamp both rects to the scrollable rect, because having either of those
|
|
// exceed the scrollable rect doesn't make sense, and could lead to false
|
|
// positives.
|
|
painted = painted.Intersect(aContentMetrics.GetScrollableRect());
|
|
showing = showing.Intersect(aContentMetrics.GetScrollableRect());
|
|
|
|
if (!painted.Contains(showing)) {
|
|
TILING_LOG("TILING: About to checkerboard; content %s\n", Stringify(aContentMetrics).c_str());
|
|
TILING_LOG("TILING: About to checkerboard; painted %s\n", Stringify(painted).c_str());
|
|
TILING_LOG("TILING: About to checkerboard; compositor %s\n", Stringify(aCompositorMetrics).c_str());
|
|
TILING_LOG("TILING: About to checkerboard; showing %s\n", Stringify(showing).c_str());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ClientMultiTiledLayerBuffer::ClientMultiTiledLayerBuffer(ClientTiledPaintedLayer* aPaintedLayer,
|
|
CompositableClient* aCompositableClient,
|
|
ClientLayerManager* aManager,
|
|
SharedFrameMetricsHelper* aHelper)
|
|
: ClientTiledLayerBuffer(aPaintedLayer, aCompositableClient)
|
|
, mManager(aManager)
|
|
, mCallback(nullptr)
|
|
, mCallbackData(nullptr)
|
|
, mSharedFrameMetricsHelper(aHelper)
|
|
{
|
|
}
|
|
|
|
bool
|
|
ClientTiledLayerBuffer::HasFormatChanged() const
|
|
{
|
|
SurfaceMode mode;
|
|
gfxContentType content = GetContentType(&mode);
|
|
return content != mLastPaintContentType ||
|
|
mode != mLastPaintSurfaceMode;
|
|
}
|
|
|
|
|
|
gfxContentType
|
|
ClientTiledLayerBuffer::GetContentType(SurfaceMode* aMode) const
|
|
{
|
|
gfxContentType content =
|
|
mPaintedLayer->CanUseOpaqueSurface() ? gfxContentType::COLOR :
|
|
gfxContentType::COLOR_ALPHA;
|
|
SurfaceMode mode = mPaintedLayer->GetSurfaceMode();
|
|
|
|
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
|
#if defined(MOZ_GFX_OPTIMIZE_MOBILE) || defined(MOZ_WIDGET_GONK)
|
|
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
|
#else
|
|
if (!mPaintedLayer->GetParent() ||
|
|
!mPaintedLayer->GetParent()->SupportsComponentAlphaChildren()) {
|
|
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
|
} else {
|
|
content = gfxContentType::COLOR;
|
|
}
|
|
#endif
|
|
} else if (mode == SurfaceMode::SURFACE_OPAQUE) {
|
|
#if defined(MOZ_GFX_OPTIMIZE_MOBILE) || defined(MOZ_WIDGET_GONK)
|
|
if (IsLowPrecision()) {
|
|
// If we're in low-res mode, drawing can sample from outside the visible
|
|
// region. Make sure that we only sample transparency if that happens.
|
|
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
|
content = gfxContentType::COLOR_ALPHA;
|
|
}
|
|
#else
|
|
if (mPaintedLayer->MayResample()) {
|
|
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
|
content = gfxContentType::COLOR_ALPHA;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (aMode) {
|
|
*aMode = mode;
|
|
}
|
|
return content;
|
|
}
|
|
|
|
class TileExpiry final : public nsExpirationTracker<TileClient, 3>
|
|
{
|
|
public:
|
|
TileExpiry() : nsExpirationTracker<TileClient, 3>(1000, "TileExpiry") {}
|
|
|
|
static void AddTile(TileClient* aTile)
|
|
{
|
|
if (!sTileExpiry) {
|
|
sTileExpiry = MakeUnique<TileExpiry>();
|
|
}
|
|
|
|
sTileExpiry->AddObject(aTile);
|
|
}
|
|
|
|
static void RemoveTile(TileClient* aTile)
|
|
{
|
|
MOZ_ASSERT(sTileExpiry);
|
|
sTileExpiry->RemoveObject(aTile);
|
|
}
|
|
|
|
static void Shutdown() {
|
|
sTileExpiry = nullptr;
|
|
}
|
|
private:
|
|
virtual void NotifyExpired(TileClient* aTile) override
|
|
{
|
|
aTile->DiscardBackBuffer();
|
|
}
|
|
|
|
static UniquePtr<TileExpiry> sTileExpiry;
|
|
};
|
|
UniquePtr<TileExpiry> TileExpiry::sTileExpiry;
|
|
|
|
void ShutdownTileCache()
|
|
{
|
|
TileExpiry::Shutdown();
|
|
}
|
|
|
|
void
|
|
TileClient::PrivateProtector::Set(TileClient * const aContainer, RefPtr<TextureClient> aNewValue)
|
|
{
|
|
if (mBuffer) {
|
|
TileExpiry::RemoveTile(aContainer);
|
|
}
|
|
mBuffer = aNewValue;
|
|
if (mBuffer) {
|
|
TileExpiry::AddTile(aContainer);
|
|
}
|
|
}
|
|
|
|
void
|
|
TileClient::PrivateProtector::Set(TileClient * const aContainer, TextureClient* aNewValue)
|
|
{
|
|
Set(aContainer, RefPtr<TextureClient>(aNewValue));
|
|
}
|
|
|
|
// Placeholder
|
|
TileClient::TileClient()
|
|
: mCompositableClient(nullptr), mWasPlaceholder(false)
|
|
{
|
|
}
|
|
|
|
TileClient::~TileClient()
|
|
{
|
|
if (mExpirationState.IsTracked()) {
|
|
MOZ_ASSERT(mBackBuffer);
|
|
TileExpiry::RemoveTile(this);
|
|
}
|
|
}
|
|
|
|
TileClient::TileClient(const TileClient& o)
|
|
{
|
|
mBackBuffer.Set(this, o.mBackBuffer);
|
|
mBackBufferOnWhite = o.mBackBufferOnWhite;
|
|
mFrontBuffer = o.mFrontBuffer;
|
|
mFrontBufferOnWhite = o.mFrontBufferOnWhite;
|
|
mCompositableClient = o.mCompositableClient;
|
|
mWasPlaceholder = o.mWasPlaceholder;
|
|
mUpdateRect = o.mUpdateRect;
|
|
#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
|
|
mLastUpdate = o.mLastUpdate;
|
|
#endif
|
|
mManager = o.mManager;
|
|
mAllocator = o.mAllocator;
|
|
mInvalidFront = o.mInvalidFront;
|
|
mInvalidBack = o.mInvalidBack;
|
|
}
|
|
|
|
TileClient&
|
|
TileClient::operator=(const TileClient& o)
|
|
{
|
|
if (this == &o) return *this;
|
|
mBackBuffer.Set(this, o.mBackBuffer);
|
|
mBackBufferOnWhite = o.mBackBufferOnWhite;
|
|
mFrontBuffer = o.mFrontBuffer;
|
|
mFrontBufferOnWhite = o.mFrontBufferOnWhite;
|
|
mCompositableClient = o.mCompositableClient;
|
|
mWasPlaceholder = o.mWasPlaceholder;
|
|
mUpdateRect = o.mUpdateRect;
|
|
#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
|
|
mLastUpdate = o.mLastUpdate;
|
|
#endif
|
|
mManager = o.mManager;
|
|
mAllocator = o.mAllocator;
|
|
mInvalidFront = o.mInvalidFront;
|
|
mInvalidBack = o.mInvalidBack;
|
|
return *this;
|
|
}
|
|
|
|
void
|
|
TileClient::Dump(std::stringstream& aStream)
|
|
{
|
|
aStream << "TileClient(bb=" << (TextureClient*)mBackBuffer << " fb=" << mFrontBuffer.get();
|
|
if (mBackBufferOnWhite) {
|
|
aStream << " bbow=" << mBackBufferOnWhite.get();
|
|
}
|
|
if (mFrontBufferOnWhite) {
|
|
aStream << " fbow=" << mFrontBufferOnWhite.get();
|
|
}
|
|
aStream << ")";
|
|
}
|
|
|
|
void
|
|
TileClient::Flip()
|
|
{
|
|
RefPtr<TextureClient> frontBuffer = mFrontBuffer;
|
|
RefPtr<TextureClient> frontBufferOnWhite = mFrontBufferOnWhite;
|
|
mFrontBuffer = mBackBuffer;
|
|
mFrontBufferOnWhite = mBackBufferOnWhite;
|
|
mBackBuffer.Set(this, frontBuffer);
|
|
mBackBufferOnWhite = frontBufferOnWhite;
|
|
nsIntRegion invalidFront = mInvalidFront;
|
|
mInvalidFront = mInvalidBack;
|
|
mInvalidBack = invalidFront;
|
|
}
|
|
|
|
static bool
|
|
CopyFrontToBack(TextureClient* aFront,
|
|
TextureClient* aBack,
|
|
const gfx::IntRect& aRectToCopy)
|
|
{
|
|
TextureClientAutoLock frontLock(aFront, OpenMode::OPEN_READ);
|
|
if (!frontLock.Succeeded()) {
|
|
gfxCriticalError() << "[Tiling:Client] Failed to lock the tile's front buffer";
|
|
return false;
|
|
}
|
|
|
|
if (!aBack->Lock(OpenMode::OPEN_READ_WRITE)) {
|
|
gfxCriticalError() << "[Tiling:Client] Failed to lock the tile's back buffer";
|
|
return false;
|
|
}
|
|
|
|
gfx::IntPoint rectToCopyTopLeft = aRectToCopy.TopLeft();
|
|
aFront->CopyToTextureClient(aBack, &aRectToCopy, &rectToCopyTopLeft);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
TileClient::ValidateBackBufferFromFront(const nsIntRegion& aDirtyRegion,
|
|
nsIntRegion& aAddPaintedRegion)
|
|
{
|
|
if (mBackBuffer && mFrontBuffer) {
|
|
gfx::IntSize tileSize = mFrontBuffer->GetSize();
|
|
const IntRect tileRect = IntRect(0, 0, tileSize.width, tileSize.height);
|
|
|
|
if (aDirtyRegion.Contains(tileRect)) {
|
|
// The dirty region means that we no longer need the front buffer, so
|
|
// discard it.
|
|
DiscardFrontBuffer();
|
|
} else {
|
|
// Region that needs copying.
|
|
nsIntRegion regionToCopy = mInvalidBack;
|
|
|
|
regionToCopy.Sub(regionToCopy, aDirtyRegion);
|
|
|
|
aAddPaintedRegion = regionToCopy;
|
|
|
|
if (regionToCopy.IsEmpty()) {
|
|
// Just redraw it all.
|
|
return;
|
|
}
|
|
|
|
// Copy the bounding rect of regionToCopy. As tiles are quite small, it
|
|
// is unlikely that we'd save much by copying each individual rect of the
|
|
// region, but we can reevaluate this if it becomes an issue.
|
|
const IntRect rectToCopy = regionToCopy.GetBounds();
|
|
gfx::IntRect gfxRectToCopy(rectToCopy.x, rectToCopy.y, rectToCopy.width, rectToCopy.height);
|
|
CopyFrontToBack(mFrontBuffer, mBackBuffer, gfxRectToCopy);
|
|
|
|
if (mBackBufferOnWhite) {
|
|
MOZ_ASSERT(mFrontBufferOnWhite);
|
|
CopyFrontToBack(mFrontBufferOnWhite, mBackBufferOnWhite, gfxRectToCopy);
|
|
}
|
|
|
|
mInvalidBack.SetEmpty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TileClient::DiscardFrontBuffer()
|
|
{
|
|
if (mFrontBuffer) {
|
|
MOZ_ASSERT(mFrontBuffer->GetReadLock());
|
|
|
|
mAllocator->ReturnTextureClientDeferred(mFrontBuffer);
|
|
if (mFrontBufferOnWhite) {
|
|
mAllocator->ReturnTextureClientDeferred(mFrontBufferOnWhite);
|
|
}
|
|
if (mFrontBuffer->IsLocked()) {
|
|
mFrontBuffer->Unlock();
|
|
}
|
|
if (mFrontBufferOnWhite && mFrontBufferOnWhite->IsLocked()) {
|
|
mFrontBufferOnWhite->Unlock();
|
|
}
|
|
mFrontBuffer = nullptr;
|
|
mFrontBufferOnWhite = nullptr;
|
|
}
|
|
}
|
|
|
|
static void
|
|
DiscardTexture(TextureClient* aTexture, TextureClientAllocator* aAllocator)
|
|
{
|
|
if (aTexture) {
|
|
MOZ_ASSERT(aTexture->GetReadLock());
|
|
if (!aTexture->HasSynchronization() && aTexture->IsReadLocked()) {
|
|
// Our current back-buffer is still locked by the compositor. This can occur
|
|
// when the client is producing faster than the compositor can consume. In
|
|
// this case we just want to drop it and not return it to the pool.
|
|
aAllocator->ReportClientLost();
|
|
} else {
|
|
aAllocator->ReturnTextureClientDeferred(aTexture);
|
|
}
|
|
if (aTexture->IsLocked()) {
|
|
aTexture->Unlock();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TileClient::DiscardBackBuffer()
|
|
{
|
|
DiscardTexture(mBackBuffer, mAllocator);
|
|
mBackBuffer.Set(this, nullptr);
|
|
DiscardTexture(mBackBufferOnWhite, mAllocator);
|
|
mBackBufferOnWhite = nullptr;
|
|
}
|
|
|
|
static already_AddRefed<TextureClient>
|
|
CreateBackBufferTexture(TextureClient* aCurrentTexture,
|
|
CompositableClient* aCompositable,
|
|
TextureClientAllocator* aAllocator)
|
|
{
|
|
if (aCurrentTexture) {
|
|
// Our current back-buffer is still locked by the compositor. This can occur
|
|
// when the client is producing faster than the compositor can consume. In
|
|
// this case we just want to drop it and not return it to the pool.
|
|
aAllocator->ReportClientLost();
|
|
}
|
|
|
|
RefPtr<TextureClient> texture = aAllocator->GetTextureClient();
|
|
|
|
if (!texture) {
|
|
gfxCriticalError() << "[Tiling:Client] Failed to allocate a TextureClient";
|
|
return nullptr;
|
|
}
|
|
|
|
texture->EnableReadLock();
|
|
|
|
if (!aCompositable->AddTextureClient(texture)) {
|
|
gfxCriticalError() << "[Tiling:Client] Failed to connect a TextureClient";
|
|
return nullptr;
|
|
}
|
|
|
|
return texture.forget();
|
|
}
|
|
|
|
TextureClient*
|
|
TileClient::GetBackBuffer(const nsIntRegion& aDirtyRegion,
|
|
gfxContentType aContent,
|
|
SurfaceMode aMode,
|
|
nsIntRegion& aAddPaintedRegion,
|
|
RefPtr<TextureClient>* aBackBufferOnWhite)
|
|
{
|
|
if (aMode != SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
|
// It can happen that a component-alpha layer stops being on component alpha
|
|
// on the next frame, just drop the buffers on white if that happens.
|
|
if (mBackBufferOnWhite) {
|
|
mAllocator->ReportClientLost();
|
|
mBackBufferOnWhite = nullptr;
|
|
}
|
|
if (mFrontBufferOnWhite) {
|
|
mAllocator->ReportClientLost();
|
|
mFrontBufferOnWhite = nullptr;
|
|
}
|
|
}
|
|
|
|
// Try to re-use the front-buffer if possible
|
|
if (mFrontBuffer &&
|
|
mFrontBuffer->HasIntermediateBuffer() &&
|
|
!mFrontBuffer->IsReadLocked() &&
|
|
(aMode != SurfaceMode::SURFACE_COMPONENT_ALPHA || (
|
|
mFrontBufferOnWhite && !mFrontBufferOnWhite->IsReadLocked()))) {
|
|
// If we had a backbuffer we no longer care about it since we'll
|
|
// re-use the front buffer.
|
|
DiscardBackBuffer();
|
|
Flip();
|
|
} else {
|
|
if (!mBackBuffer || mBackBuffer->IsReadLocked()) {
|
|
mBackBuffer.Set(this,
|
|
CreateBackBufferTexture(mBackBuffer, mCompositableClient, mAllocator)
|
|
);
|
|
if (!mBackBuffer) {
|
|
DiscardBackBuffer();
|
|
DiscardFrontBuffer();
|
|
return nullptr;
|
|
}
|
|
mInvalidBack = IntRect(IntPoint(), mBackBuffer->GetSize());
|
|
}
|
|
|
|
if (aMode == SurfaceMode::SURFACE_COMPONENT_ALPHA
|
|
&& (!mBackBufferOnWhite || mBackBufferOnWhite->IsReadLocked())) {
|
|
mBackBufferOnWhite = CreateBackBufferTexture(
|
|
mBackBufferOnWhite, mCompositableClient, mAllocator
|
|
);
|
|
if (!mBackBufferOnWhite) {
|
|
DiscardBackBuffer();
|
|
DiscardFrontBuffer();
|
|
return nullptr;
|
|
}
|
|
mInvalidBack = IntRect(IntPoint(), mBackBufferOnWhite->GetSize());
|
|
}
|
|
|
|
ValidateBackBufferFromFront(aDirtyRegion, aAddPaintedRegion);
|
|
}
|
|
|
|
if (!mBackBuffer->IsLocked()) {
|
|
if (!mBackBuffer->Lock(OpenMode::OPEN_READ_WRITE)) {
|
|
gfxCriticalError() << "[Tiling:Client] Failed to lock a tile (B)";
|
|
DiscardBackBuffer();
|
|
DiscardFrontBuffer();
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if (mBackBufferOnWhite && !mBackBufferOnWhite->IsLocked()) {
|
|
if (!mBackBufferOnWhite->Lock(OpenMode::OPEN_READ_WRITE)) {
|
|
gfxCriticalError() << "[Tiling:Client] Failed to lock a tile (W)";
|
|
DiscardBackBuffer();
|
|
DiscardFrontBuffer();
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
*aBackBufferOnWhite = mBackBufferOnWhite;
|
|
return mBackBuffer;
|
|
}
|
|
|
|
TileDescriptor
|
|
TileClient::GetTileDescriptor()
|
|
{
|
|
if (IsPlaceholderTile()) {
|
|
mWasPlaceholder = true;
|
|
return PlaceholderTileDescriptor();
|
|
}
|
|
bool wasPlaceholder = mWasPlaceholder;
|
|
mWasPlaceholder = false;
|
|
|
|
ReadLockDescriptor lock;
|
|
mFrontBuffer->SerializeReadLock(lock);
|
|
|
|
ReadLockDescriptor lockOnWhite = null_t();
|
|
if (mFrontBufferOnWhite) {
|
|
mFrontBufferOnWhite->SerializeReadLock(lockOnWhite);
|
|
}
|
|
|
|
return TexturedTileDescriptor(nullptr, mFrontBuffer->GetIPDLActor(),
|
|
mFrontBufferOnWhite ? MaybeTexture(mFrontBufferOnWhite->GetIPDLActor()) : MaybeTexture(null_t()),
|
|
mUpdateRect,
|
|
lock, lockOnWhite,
|
|
wasPlaceholder);
|
|
}
|
|
|
|
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,
|
|
bool aIsProgressive)
|
|
{
|
|
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 = aIsProgressive;
|
|
|
|
#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
|
|
|
|
PROFILER_LABEL("ClientMultiTiledLayerBuffer", "PaintThebesUpdate",
|
|
js::ProfileEntry::Category::GRAPHICS);
|
|
|
|
mNewValidRegion = aNewValidRegion;
|
|
Update(aNewValidRegion, aPaintRegion, aDirtyRegion);
|
|
|
|
#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 PadDrawTargetOutFromRegion(RefPtr<DrawTarget> drawTarget, nsIntRegion ®ion)
|
|
{
|
|
struct LockedBits {
|
|
uint8_t *data;
|
|
IntSize size;
|
|
int32_t stride;
|
|
SurfaceFormat format;
|
|
static int clamp(int x, int min, int max)
|
|
{
|
|
if (x < min)
|
|
x = min;
|
|
if (x > max)
|
|
x = max;
|
|
return x;
|
|
}
|
|
|
|
static void ensure_memcpy(uint8_t *dst, uint8_t *src, size_t n, uint8_t *bitmap, int stride, int height)
|
|
{
|
|
if (src + n > bitmap + stride*height) {
|
|
MOZ_CRASH("GFX: long src memcpy");
|
|
}
|
|
if (src < bitmap) {
|
|
MOZ_CRASH("GFX: short src memcpy");
|
|
}
|
|
if (dst + n > bitmap + stride*height) {
|
|
MOZ_CRASH("GFX: long dst mempcy");
|
|
}
|
|
if (dst < bitmap) {
|
|
MOZ_CRASH("GFX: short dst mempcy");
|
|
}
|
|
}
|
|
|
|
static void visitor(void *closure, VisitSide side, int x1, int y1, int x2, int y2) {
|
|
LockedBits *lb = static_cast<LockedBits*>(closure);
|
|
uint8_t *bitmap = lb->data;
|
|
const int bpp = gfx::BytesPerPixel(lb->format);
|
|
const int stride = lb->stride;
|
|
const int width = lb->size.width;
|
|
const int height = lb->size.height;
|
|
|
|
if (side == VisitSide::TOP) {
|
|
if (y1 > 0) {
|
|
x1 = clamp(x1, 0, width - 1);
|
|
x2 = clamp(x2, 0, width - 1);
|
|
ensure_memcpy(&bitmap[x1*bpp + (y1-1) * stride], &bitmap[x1*bpp + y1 * stride], (x2 - x1) * bpp, bitmap, stride, height);
|
|
memcpy(&bitmap[x1*bpp + (y1-1) * stride], &bitmap[x1*bpp + y1 * stride], (x2 - x1) * bpp);
|
|
}
|
|
} else if (side == VisitSide::BOTTOM) {
|
|
if (y1 < height) {
|
|
x1 = clamp(x1, 0, width - 1);
|
|
x2 = clamp(x2, 0, width - 1);
|
|
ensure_memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[x1*bpp + (y1-1) * stride], (x2 - x1) * bpp, bitmap, stride, height);
|
|
memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[x1*bpp + (y1-1) * stride], (x2 - x1) * bpp);
|
|
}
|
|
} else if (side == VisitSide::LEFT) {
|
|
if (x1 > 0) {
|
|
while (y1 != y2) {
|
|
memcpy(&bitmap[(x1-1)*bpp + y1 * stride], &bitmap[x1*bpp + y1*stride], bpp);
|
|
y1++;
|
|
}
|
|
}
|
|
} else if (side == VisitSide::RIGHT) {
|
|
if (x1 < width) {
|
|
while (y1 != y2) {
|
|
memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[(x1-1)*bpp + y1*stride], bpp);
|
|
y1++;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
} lb;
|
|
|
|
if (drawTarget->LockBits(&lb.data, &lb.size, &lb.stride, &lb.format)) {
|
|
// we can only pad software targets so if we can't lock the bits don't pad
|
|
region.VisitEdges(lb.visitor, &lb);
|
|
drawTarget->ReleaseBits(lb.data);
|
|
}
|
|
}
|
|
|
|
void
|
|
ClientTiledLayerBuffer::UnlockTile(TileClient& aTile)
|
|
{
|
|
// We locked the back buffer, and flipped so we now need to unlock the front
|
|
if (aTile.mFrontBuffer && aTile.mFrontBuffer->IsLocked()) {
|
|
aTile.mFrontBuffer->Unlock();
|
|
aTile.mFrontBuffer->SyncWithObject(mCompositableClient->GetForwarder()->GetSyncObject());
|
|
}
|
|
if (aTile.mFrontBufferOnWhite && aTile.mFrontBufferOnWhite->IsLocked()) {
|
|
aTile.mFrontBufferOnWhite->Unlock();
|
|
aTile.mFrontBufferOnWhite->SyncWithObject(mCompositableClient->GetForwarder()->GetSyncObject());
|
|
}
|
|
if (aTile.mBackBuffer && aTile.mBackBuffer->IsLocked()) {
|
|
aTile.mBackBuffer->Unlock();
|
|
}
|
|
if (aTile.mBackBufferOnWhite && aTile.mBackBufferOnWhite->IsLocked()) {
|
|
aTile.mBackBufferOnWhite->Unlock();
|
|
}
|
|
}
|
|
|
|
void ClientMultiTiledLayerBuffer::Update(const nsIntRegion& newValidRegion,
|
|
const nsIntRegion& aPaintRegion,
|
|
const nsIntRegion& aDirtyRegion)
|
|
{
|
|
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 TileIntPoint tilePosition = oldTiles.TilePosition(oldIndex);
|
|
const size_t newIndex = newTiles.TileIndex(tilePosition);
|
|
// 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(tilePosition)) {
|
|
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();
|
|
|
|
if (!aPaintRegion.IsEmpty()) {
|
|
for (size_t i = 0; i < newTileCount; ++i) {
|
|
const TileIntPoint tilePosition = newTiles.TilePosition(i);
|
|
|
|
IntPoint tileOffset = GetTileOffset(tilePosition);
|
|
nsIntRegion tileDrawRegion = IntRect(tileOffset, scaledTileSize);
|
|
tileDrawRegion.AndWith(aPaintRegion);
|
|
|
|
if (tileDrawRegion.IsEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
TileClient& tile = mRetainedTiles[i];
|
|
if (!ValidateTile(tile, GetTileOffset(tilePosition), tileDrawRegion)) {
|
|
gfxCriticalError() << "ValidateTile failed";
|
|
}
|
|
}
|
|
|
|
if (mMoz2DTiles.size() > 0) {
|
|
gfx::TileSet tileset;
|
|
for (size_t i = 0; i < mMoz2DTiles.size(); ++i) {
|
|
mMoz2DTiles[i].mTileOrigin -= mTilingOrigin;
|
|
}
|
|
tileset.mTiles = &mMoz2DTiles[0];
|
|
tileset.mTileCount = mMoz2DTiles.size();
|
|
RefPtr<DrawTarget> drawTarget = gfx::Factory::CreateTiledDrawTarget(tileset);
|
|
if (!drawTarget || !drawTarget->IsValid()) {
|
|
gfxDevCrash(LogReason::InvalidContext) << "Invalid tiled draw target";
|
|
return;
|
|
}
|
|
drawTarget->SetTransform(Matrix());
|
|
|
|
RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(drawTarget);
|
|
MOZ_ASSERT(ctx); // already checked the draw target above
|
|
ctx->SetMatrix(
|
|
ctx->CurrentMatrix().Scale(mResolution, mResolution).Translate(ThebesPoint(-mTilingOrigin)));
|
|
|
|
mCallback(mPaintedLayer, ctx, aPaintRegion, aDirtyRegion,
|
|
DrawRegionClip::DRAW, nsIntRegion(), mCallbackData);
|
|
mMoz2DTiles.clear();
|
|
// Reset:
|
|
mTilingOrigin = IntPoint(std::numeric_limits<int32_t>::max(),
|
|
std::numeric_limits<int32_t>::max());
|
|
}
|
|
|
|
bool edgePaddingEnabled = gfxPrefs::TileEdgePaddingEnabled();
|
|
|
|
for (uint32_t i = 0; i < mRetainedTiles.Length(); ++i) {
|
|
TileClient& tile = mRetainedTiles[i];
|
|
|
|
// Only worry about padding when not doing low-res because it simplifies
|
|
// the math and the artifacts won't be noticable
|
|
// Edge padding prevents sampling artifacts when compositing.
|
|
if (edgePaddingEnabled && mResolution == 1 &&
|
|
tile.mFrontBuffer && tile.mFrontBuffer->IsLocked()) {
|
|
|
|
const TileIntPoint tilePosition = newTiles.TilePosition(i);
|
|
IntPoint tileOffset = GetTileOffset(tilePosition);
|
|
// Strictly speakig we want the unscaled rect here, but it doesn't matter
|
|
// because we only run this code when the resolution is equal to 1.
|
|
IntRect tileRect = IntRect(tileOffset.x, tileOffset.y,
|
|
GetTileSize().width, GetTileSize().height);
|
|
|
|
nsIntRegion tileDrawRegion = IntRect(tileOffset, scaledTileSize);
|
|
tileDrawRegion.AndWith(aPaintRegion);
|
|
|
|
nsIntRegion tileValidRegion = mValidRegion;
|
|
tileValidRegion.OrWith(tileDrawRegion);
|
|
|
|
// We only need to pad out if the tile has area that's not valid
|
|
if (!tileValidRegion.Contains(tileRect)) {
|
|
tileValidRegion = tileValidRegion.Intersect(tileRect);
|
|
// translate the region into tile space and pad
|
|
tileValidRegion.MoveBy(-IntPoint(tileOffset.x, tileOffset.y));
|
|
RefPtr<DrawTarget> drawTarget = tile.mFrontBuffer->BorrowDrawTarget();
|
|
PadDrawTargetOutFromRegion(drawTarget, tileValidRegion);
|
|
}
|
|
}
|
|
UnlockTile(tile);
|
|
}
|
|
}
|
|
|
|
mTiles = newTiles;
|
|
mValidRegion = newValidRegion;
|
|
mPaintedRegion.OrWith(aPaintRegion);
|
|
}
|
|
|
|
bool
|
|
ClientMultiTiledLayerBuffer::ValidateTile(TileClient& aTile,
|
|
const nsIntPoint& aTileOrigin,
|
|
const nsIntRegion& aDirtyRegion)
|
|
{
|
|
PROFILER_LABEL("ClientMultiTiledLayerBuffer", "ValidateTile",
|
|
js::ProfileEntry::Category::GRAPHICS);
|
|
|
|
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
|
|
if (aDirtyRegion.IsComplex()) {
|
|
printf_stderr("Complex region\n");
|
|
}
|
|
#endif
|
|
|
|
SurfaceMode mode;
|
|
gfxContentType content = GetContentType(&mode);
|
|
|
|
if (aTile.IsPlaceholderTile()) {
|
|
aTile.SetLayerManager(mManager);
|
|
aTile.SetTextureAllocator(mManager->GetCompositorBridgeChild()->GetTexturePool(
|
|
mManager->GetCompositorBackendType(),
|
|
gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content),
|
|
TextureFlags::DISALLOW_BIGIMAGE | TextureFlags::IMMEDIATE_UPLOAD));
|
|
}
|
|
aTile.SetCompositableClient(mCompositableClient);
|
|
|
|
nsIntRegion offsetScaledDirtyRegion = aDirtyRegion.MovedBy(-aTileOrigin);
|
|
offsetScaledDirtyRegion.ScaleRoundOut(mResolution, mResolution);
|
|
|
|
nsIntRegion extraPainted;
|
|
RefPtr<TextureClient> backBufferOnWhite;
|
|
RefPtr<TextureClient> backBuffer =
|
|
aTile.GetBackBuffer(offsetScaledDirtyRegion,
|
|
content, mode,
|
|
extraPainted,
|
|
&backBufferOnWhite);
|
|
|
|
aTile.mUpdateRect = offsetScaledDirtyRegion.GetBounds().Union(extraPainted.GetBounds());
|
|
|
|
extraPainted.MoveBy(aTileOrigin);
|
|
extraPainted.And(extraPainted, mNewValidRegion);
|
|
mPaintedRegion.Or(mPaintedRegion, extraPainted);
|
|
|
|
if (!backBuffer) {
|
|
return false;
|
|
}
|
|
|
|
gfx::Tile moz2DTile;
|
|
RefPtr<DrawTarget> dt = backBuffer->BorrowDrawTarget();
|
|
RefPtr<DrawTarget> dtOnWhite;
|
|
if (backBufferOnWhite) {
|
|
dtOnWhite = backBufferOnWhite->BorrowDrawTarget();
|
|
moz2DTile.mDrawTarget = Factory::CreateDualDrawTarget(dt, dtOnWhite);
|
|
} else {
|
|
moz2DTile.mDrawTarget = dt;
|
|
}
|
|
moz2DTile.mTileOrigin = gfx::IntPoint(aTileOrigin.x, aTileOrigin.y);
|
|
if (!dt || (backBufferOnWhite && !dtOnWhite)) {
|
|
aTile.DiscardBuffers();
|
|
return false;
|
|
}
|
|
|
|
mMoz2DTiles.push_back(moz2DTile);
|
|
mTilingOrigin.x = std::min(mTilingOrigin.x, moz2DTile.mTileOrigin.x);
|
|
mTilingOrigin.y = std::min(mTilingOrigin.y, moz2DTile.mTileOrigin.y);
|
|
|
|
for (auto iter = aDirtyRegion.RectIter(); !iter.Done(); iter.Next()) {
|
|
const IntRect& dirtyRect = iter.Get();
|
|
gfx::Rect drawRect(dirtyRect.x - aTileOrigin.x,
|
|
dirtyRect.y - aTileOrigin.y,
|
|
dirtyRect.width,
|
|
dirtyRect.height);
|
|
drawRect.Scale(mResolution);
|
|
|
|
// Mark the newly updated area as invalid in the front buffer
|
|
aTile.mInvalidFront.Or(aTile.mInvalidFront,
|
|
IntRect(NS_lroundf(drawRect.x), NS_lroundf(drawRect.y),
|
|
drawRect.width, drawRect.height));
|
|
|
|
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
|
dt->FillRect(drawRect, ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
|
|
dtOnWhite->FillRect(drawRect, ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
|
|
} else if (content == gfxContentType::COLOR_ALPHA) {
|
|
dt->ClearRect(drawRect);
|
|
}
|
|
}
|
|
|
|
// The new buffer is now validated, remove the dirty region from it.
|
|
aTile.mInvalidBack.SubOut(offsetScaledDirtyRegion);
|
|
|
|
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;
|
|
#if defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_ANDROID_APZ)
|
|
FrameMetrics contentMetrics = scrollAncestor.Metrics();
|
|
bool abortPaint = false;
|
|
// On Android, only the primary scrollable layer is async-scrolled, and the only one
|
|
// that the Java-side code can provide details about. If we're tiling some other layer
|
|
// then we already have all the information we need about it.
|
|
if (contentMetrics.GetScrollId() == mManager->GetRootScrollableLayerId()) {
|
|
FrameMetrics compositorMetrics = contentMetrics;
|
|
// The ProgressiveUpdateCallback updates the compositorMetrics
|
|
abortPaint = mManager->ProgressiveUpdateCallback(!staleRegion.Contains(aInvalidRegion),
|
|
compositorMetrics,
|
|
!drawingLowPrecision);
|
|
viewTransform = ComputeViewTransform(contentMetrics, compositorMetrics);
|
|
}
|
|
#else
|
|
MOZ_ASSERT(mSharedFrameMetricsHelper);
|
|
|
|
bool abortPaint =
|
|
mSharedFrameMetricsHelper->UpdateFromCompositorFrameMetrics(
|
|
scrollAncestor,
|
|
!staleRegion.Contains(aInvalidRegion),
|
|
drawingLowPrecision,
|
|
viewTransform);
|
|
#endif
|
|
|
|
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) {
|
|
PROFILER_LABEL("ClientMultiTiledLayerBuffer", "ComputeProgressiveUpdateRegion",
|
|
js::ProfileEntry::Category::GRAPHICS);
|
|
|
|
aRegionToPaint.SetEmpty();
|
|
return aIsRepeated;
|
|
}
|
|
}
|
|
|
|
Maybe<LayerRect> transformedCompositionBounds =
|
|
GetCompositorSideCompositionBounds(scrollAncestor,
|
|
aPaintData->mTransformToCompBounds,
|
|
viewTransform,
|
|
ViewAs<LayerPixel>(Rect(mPaintedLayer->GetLayerBounds())));
|
|
|
|
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.
|
|
// On B2G uploads are cheaper and we value coherency more, especially outside
|
|
// the browser, so we always use the entire user-visible area.
|
|
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.x += incX;
|
|
} else {
|
|
tileBounds.y += 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(nsIntRegion& aValidRegion,
|
|
nsIntRegion& aInvalidRegion,
|
|
const nsIntRegion& aOldValidRegion,
|
|
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;
|
|
do {
|
|
// Compute the region that should be updated. Repeat as many times as
|
|
// is required.
|
|
nsIntRegion regionToPaint;
|
|
repeat = ComputeProgressiveUpdateRegion(aInvalidRegion,
|
|
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.
|
|
aValidRegion.Or(aValidRegion, 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(aValidRegion, aOldValidRegion);
|
|
|
|
// Paint the computed region and subtract it from the invalid region.
|
|
PaintThebes(validOrStale, regionToPaint, aInvalidRegion,
|
|
aCallback, aCallbackData, true);
|
|
aInvalidRegion.Sub(aInvalidRegion, regionToPaint);
|
|
} while (repeat);
|
|
|
|
TILING_LOG("TILING %p: Progressive update final valid region %s buffer changed %d\n", mPaintedLayer, Stringify(aValidRegion).c_str(), isBufferChanged);
|
|
TILING_LOG("TILING %p: Progressive update final invalid region %s\n", mPaintedLayer, Stringify(aInvalidRegion).c_str());
|
|
|
|
// Return false if nothing has been drawn, or give what has been drawn
|
|
// to the shadow layer to upload.
|
|
return isBufferChanged;
|
|
}
|
|
|
|
void
|
|
TiledContentClient::PrintInfo(std::stringstream& aStream, const char* aPrefix)
|
|
{
|
|
aStream << aPrefix;
|
|
aStream << nsPrintfCString("%sTiledContentClient (0x%p)", mName, this).get();
|
|
|
|
if (profiler_feature_active("displaylistdump")) {
|
|
nsAutoCString pfx(aPrefix);
|
|
pfx += " ";
|
|
|
|
Dump(aStream, pfx.get(), false);
|
|
}
|
|
}
|
|
|
|
void
|
|
TiledContentClient::Dump(std::stringstream& aStream,
|
|
const char* aPrefix,
|
|
bool aDumpHtml,
|
|
TextureDumpMode aCompress)
|
|
{
|
|
GetTiledBuffer()->Dump(aStream, aPrefix, aDumpHtml, aCompress);
|
|
}
|
|
|
|
void
|
|
BasicTiledLayerPaintData::ResetPaintData()
|
|
{
|
|
mLowPrecisionPaintCount = 0;
|
|
mPaintFinished = false;
|
|
mHasTransformAnimation = false;
|
|
mCompositionBounds.SetEmpty();
|
|
mCriticalDisplayPort = Nothing();
|
|
}
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|