gecko/gfx/layers/basic/BasicTiledThebesLayer.cpp
Chris Lord bb3f3cc9cd Bug 794200 - Progressive draw tiles in the direction of scrolling. r=bgirard
When scrolling downwards, make sure to fill in tiles from the top, upwards
from the bottom, left from the right and right from the left.
2012-09-26 21:41:56 +01:00

351 lines
12 KiB
C++

/* 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/PLayersChild.h"
#include "BasicTiledThebesLayer.h"
#include "gfxImageSurface.h"
#include "sampler.h"
#include "gfxPlatform.h"
#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
#include "cairo.h"
#include <sstream>
using mozilla::layers::Layer;
static void DrawDebugOverlay(gfxImageSurface* imgSurf, int x, int y)
{
gfxContext c(imgSurf);
// Draw border
c.NewPath();
c.SetDeviceColor(gfxRGBA(0.0, 0.0, 0.0, 1.0));
c.Rectangle(gfxRect(gfxPoint(0,0),imgSurf->GetSize()));
c.Stroke();
// Build tile description
std::stringstream ss;
ss << x << ", " << y;
// Draw text using cairo toy text API
cairo_t* cr = c.GetCairo();
cairo_set_font_size(cr, 10);
cairo_text_extents_t extents;
cairo_text_extents(cr, ss.str().c_str(), &extents);
int textWidth = extents.width + 6;
c.NewPath();
c.SetDeviceColor(gfxRGBA(0.0, 0.0, 0.0, 1.0));
c.Rectangle(gfxRect(gfxPoint(2,2),gfxSize(textWidth, 15)));
c.Fill();
c.NewPath();
c.SetDeviceColor(gfxRGBA(1.0, 0.0, 0.0, 1.0));
c.Rectangle(gfxRect(gfxPoint(2,2),gfxSize(textWidth, 15)));
c.Stroke();
c.NewPath();
cairo_move_to(cr, 4, 13);
cairo_show_text(cr, ss.str().c_str());
}
#endif
namespace mozilla {
namespace layers {
bool
BasicTiledLayerBuffer::HasFormatChanged(BasicTiledThebesLayer* aThebesLayer) const
{
return aThebesLayer->CanUseOpaqueSurface() != mLastPaintOpaque;
}
gfxASurface::gfxImageFormat
BasicTiledLayerBuffer::GetFormat() const
{
if (mThebesLayer->CanUseOpaqueSurface()) {
return gfxASurface::ImageFormatRGB16_565;
} else {
return gfxASurface::ImageFormatARGB32;
}
}
void
BasicTiledLayerBuffer::PaintThebes(BasicTiledThebesLayer* aLayer,
const nsIntRegion& aNewValidRegion,
const nsIntRegion& aPaintRegion,
LayerManager::DrawThebesLayerCallback aCallback,
void* aCallbackData)
{
mThebesLayer = aLayer;
mCallback = aCallback;
mCallbackData = aCallbackData;
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
long start = PR_IntervalNow();
#endif
if (UseSinglePaintBuffer()) {
const nsIntRect bounds = aPaintRegion.GetBounds();
{
SAMPLE_LABEL("BasicTiledLayerBuffer", "PaintThebesSingleBufferAlloc");
mSinglePaintBuffer = new gfxImageSurface(gfxIntSize(bounds.width, bounds.height), GetFormat(), !aLayer->CanUseOpaqueSurface());
mSinglePaintBufferOffset = nsIntPoint(bounds.x, bounds.y);
}
nsRefPtr<gfxContext> ctxt = new gfxContext(mSinglePaintBuffer);
ctxt->NewPath();
ctxt->Translate(gfxPoint(-bounds.x, -bounds.y));
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
if (PR_IntervalNow() - start > 3) {
printf_stderr("Slow alloc %i\n", PR_IntervalNow() - start);
}
start = PR_IntervalNow();
#endif
SAMPLE_LABEL("BasicTiledLayerBuffer", "PaintThebesSingleBufferDraw");
mCallback(mThebesLayer, ctxt, aPaintRegion, nsIntRegion(), mCallbackData);
}
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
if (PR_IntervalNow() - start > 30) {
const nsIntRect 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");
nsIntRegionRectIterator it(aPaintRegion);
for (const nsIntRect* rect = it.Next(); rect != nullptr; rect = it.Next()) {
printf_stderr(" rect %i, %i, %i, %i\n", rect->x, rect->y, rect->width, rect->height);
}
}
}
start = PR_IntervalNow();
#endif
SAMPLE_LABEL("BasicTiledLayerBuffer", "PaintThebesUpdate");
Update(aNewValidRegion, aPaintRegion);
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
if (PR_IntervalNow() - start > 10) {
const nsIntRect 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
mLastPaintOpaque = mThebesLayer->CanUseOpaqueSurface();
mThebesLayer = nullptr;
mCallback = nullptr;
mCallbackData = nullptr;
mSinglePaintBuffer = nullptr;
}
BasicTiledLayerTile
BasicTiledLayerBuffer::ValidateTileInternal(BasicTiledLayerTile aTile,
const nsIntPoint& aTileOrigin,
const nsIntRect& aDirtyRect)
{
if (aTile == GetPlaceholderTile() || aTile.mSurface->Format() != GetFormat()) {
gfxImageSurface* tmpTile = new gfxImageSurface(gfxIntSize(GetTileLength(), GetTileLength()),
GetFormat(), !mThebesLayer->CanUseOpaqueSurface());
aTile = BasicTiledLayerTile(tmpTile);
}
gfxRect drawRect(aDirtyRect.x - aTileOrigin.x, aDirtyRect.y - aTileOrigin.y,
aDirtyRect.width, aDirtyRect.height);
// Use the gfxReusableSurfaceWrapper, which will reuse the surface
// if the compositor no longer has a read lock, otherwise the surface
// will be copied into a new writable surface.
gfxImageSurface* writableSurface;
aTile.mSurface = aTile.mSurface->GetWritable(&writableSurface);
// Bug 742100, this gfxContext really should live on the stack.
nsRefPtr<gfxContext> ctxt = new gfxContext(writableSurface);
ctxt->SetOperator(gfxContext::OPERATOR_SOURCE);
if (mSinglePaintBuffer) {
ctxt->NewPath();
ctxt->SetSource(mSinglePaintBuffer.get(),
gfxPoint(mSinglePaintBufferOffset.x - aDirtyRect.x + drawRect.x,
mSinglePaintBufferOffset.y - aDirtyRect.y + drawRect.y));
ctxt->Rectangle(drawRect, true);
ctxt->Fill();
} else {
ctxt->NewPath();
ctxt->Translate(gfxPoint(-aTileOrigin.x, -aTileOrigin.y));
nsIntPoint a = aTileOrigin;
mCallback(mThebesLayer, ctxt, nsIntRegion(nsIntRect(a, nsIntSize(GetTileLength(), GetTileLength()))), aDirtyRect, mCallbackData);
}
#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
DrawDebugOverlay(writableSurface, aTileOrigin.x, aTileOrigin.y);
#endif
return aTile;
}
BasicTiledLayerTile
BasicTiledLayerBuffer::ValidateTile(BasicTiledLayerTile aTile,
const nsIntPoint& aTileOrigin,
const nsIntRegion& aDirtyRegion)
{
SAMPLE_LABEL("BasicTiledLayerBuffer", "ValidateTile");
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
if (aDirtyRegion.IsComplex()) {
printf_stderr("Complex region\n");
}
#endif
nsIntRegionRectIterator it(aDirtyRegion);
for (const nsIntRect* rect = it.Next(); rect != nullptr; rect = it.Next()) {
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
printf_stderr(" break into subrect %i, %i, %i, %i\n", rect->x, rect->y, rect->width, rect->height);
#endif
aTile = ValidateTileInternal(aTile, aTileOrigin, *rect);
}
return aTile;
}
void
BasicTiledThebesLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs)
{
aAttrs = ThebesLayerAttributes(GetValidRegion());
}
void
BasicTiledThebesLayer::PaintThebes(gfxContext* aContext,
Layer* aMaskLayer,
LayerManager::DrawThebesLayerCallback aCallback,
void* aCallbackData,
ReadbackProcessor* aReadback)
{
if (!aCallback) {
BasicManager()->SetTransactionIncomplete();
return;
}
if (!HasShadow()) {
NS_ASSERTION(false, "Shadow requested for painting\n");
return;
}
if (mTiledBuffer.HasFormatChanged(this)) {
mValidRegion = nsIntRegion();
}
nsIntRegion regionToPaint = mVisibleRegion;
regionToPaint.Sub(regionToPaint, mValidRegion);
if (regionToPaint.IsEmpty())
return;
gfxSize resolution(1, 1);
for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) {
const FrameMetrics& metrics = parent->GetFrameMetrics();
resolution.width *= metrics.mResolution.width;
resolution.height *= metrics.mResolution.height;
}
// Force immediate tile painting when the layer has changed resolution.
if (gfxPlatform::UseProgressiveTilePainting() &&
mTiledBuffer.GetResolution() == resolution) {
// Paint tiles that have no content before tiles that only have stale content.
nsIntRegion staleRegion = mTiledBuffer.GetValidRegion();
staleRegion.And(staleRegion, regionToPaint);
if (!staleRegion.IsEmpty() && !staleRegion.Contains(regionToPaint)) {
regionToPaint.Sub(regionToPaint, staleRegion);
}
// The following code decides what order to draw tiles in, based on the
// current scroll direction of the primary scrollable layer.
// XXX While this code is of a reasonable size currently, it is likely
// we'll want to add more comprehensive methods of deciding what
// tiles to draw. This is a good candidate for splitting out into a
// separate function.
gfx::Point scrollOffset(0, 0);
Layer* primaryScrollable = BasicManager()->GetPrimaryScrollableLayer();
if (primaryScrollable) {
const FrameMetrics& metrics = primaryScrollable->AsContainerLayer()->GetFrameMetrics();
scrollOffset = metrics.mViewportScrollOffset;
}
// First, decide whether to iterate on the region from the beginning or end
// of the rect list. This relies on the specific behaviour of nsRegion when
// subtracting rects. If we're moving more in the X direction, we draw
// tiles by column, otherwise by row.
nsIntRegionRectIterator it(regionToPaint);
const nsIntRect* rect;
int32_t scrollDiffX = scrollOffset.x - mLastScrollOffset.x;
int32_t scrollDiffY = scrollOffset.y - mLastScrollOffset.y;
if ((NS_ABS(scrollDiffY) > NS_ABS(scrollDiffX) && scrollDiffY >= 0)) {
rect = it.Next();
} else {
const nsIntRect* lastRect;
while (lastRect = it.Next()) {
rect = lastRect;
}
}
// Second, decide what direction to start drawing rects from by checking
// the scroll offset difference of the primary scrollable layer. If we're
// scrolling to the right, make sure to start from the left, downwards
// start from the top, etc.
int paintTileStartX, paintTileStartY;
if (scrollOffset.x >= mLastScrollOffset.x) {
paintTileStartX = mTiledBuffer.RoundDownToTileEdge(rect->x);
} else {
paintTileStartX = mTiledBuffer.RoundDownToTileEdge(rect->XMost() - 1);
}
if (scrollOffset.y >= mLastScrollOffset.y) {
paintTileStartY = mTiledBuffer.RoundDownToTileEdge(rect->y);
} else {
paintTileStartY = mTiledBuffer.RoundDownToTileEdge(rect->YMost() - 1);
}
nsIntRegion maxPaint(
nsIntRect(paintTileStartX, paintTileStartY,
mTiledBuffer.GetTileLength(), mTiledBuffer.GetTileLength()));
if (!maxPaint.Contains(regionToPaint)) {
// 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.
regionToPaint.And(regionToPaint, maxPaint);
BasicManager()->SetRepeatTransaction();
// Make sure that tiles that fall outside of the visible region are discarded.
mValidRegion.And(mValidRegion, mVisibleRegion);
} else {
// The transaction is completed, store the last scroll offset.
mLastScrollOffset = scrollOffset;
}
// Keep track of what we're about to refresh.
mValidRegion.Or(mValidRegion, regionToPaint);
} else {
mTiledBuffer.SetResolution(resolution);
mValidRegion = mVisibleRegion;
}
mTiledBuffer.PaintThebes(this, mVisibleRegion, regionToPaint, aCallback, aCallbackData);
mTiledBuffer.ReadLock();
if (aMaskLayer) {
static_cast<BasicImplData*>(aMaskLayer->ImplData())
->Paint(aContext, nullptr);
}
// Create a heap copy owned and released by the compositor. This is needed
// since we're sending this over an async message and content needs to be
// be able to modify the tiled buffer in the next transaction.
// TODO: Remove me once Bug 747811 lands.
BasicTiledLayerBuffer *heapCopy = new BasicTiledLayerBuffer(mTiledBuffer);
BasicManager()->PaintedTiledLayerBuffer(BasicManager()->Hold(this), heapCopy);
}
} // mozilla
} // layers