Bug 590294, part 8: Implement resolution-scaled drawing for basic layers. r=roc
--- a/gfx/layers/ThebesLayerBuffer.cpp
+++ b/gfx/layers/ThebesLayerBuffer.cpp
@@ -39,16 +39,29 @@
#include "Layers.h"
#include "gfxContext.h"
#include "gfxPlatform.h"
#include "gfxUtils.h"
namespace mozilla {
namespace layers {
+static nsIntSize
+ScaledSize(const nsIntSize& aSize, float aXScale, float aYScale)
+{
+ if (aXScale == 1.0 && aYScale == 1.0) {
+ return aSize;
+ }
+
+ gfxRect rect(0, 0, aSize.width, aSize.height);
+ rect.Scale(aXScale, aYScale);
+ rect.RoundOut();
+ return nsIntSize(rect.size.width, rect.size.height);
+}
+
nsIntRect
ThebesLayerBuffer::GetQuadrantRectangle(XSide aXSide, YSide aYSide)
{
// quadrantTranslation is the amount we translate the top-left
// of the quadrant by to get coordinates relative to the layer
nsIntPoint quadrantTranslation = -mBufferRotation;
quadrantTranslation.x += aXSide == LEFT ? mBufferRect.width : 0;
quadrantTranslation.y += aYSide == TOP ? mBufferRect.height : 0;
@@ -62,86 +75,118 @@ ThebesLayerBuffer::GetQuadrantRectangle(
* 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
ThebesLayerBuffer::DrawBufferQuadrant(gfxContext* aTarget,
- XSide aXSide, YSide aYSide, float aOpacity)
+ XSide aXSide, YSide aYSide,
+ float aOpacity,
+ float aXRes, float aYRes)
{
// 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
nsIntRect quadrantRect = GetQuadrantRectangle(aXSide, aYSide);
nsIntRect fillRect;
if (!fillRect.IntersectRect(mBufferRect, quadrantRect))
return;
aTarget->NewPath();
- aTarget->Rectangle(gfxRect(fillRect.x, fillRect.y, fillRect.width, fillRect.height),
+ aTarget->Rectangle(gfxRect(fillRect.x, fillRect.y,
+ fillRect.width, fillRect.height),
PR_TRUE);
- aTarget->SetSource(mBuffer, gfxPoint(quadrantRect.x, quadrantRect.y));
+
+ gfxPoint quadrantTranslation(quadrantRect.x, quadrantRect.y);
+ nsRefPtr<gfxPattern> pattern = new gfxPattern(mBuffer);
+
+ // Transform from user -> buffer space.
+ gfxMatrix transform;
+ transform.Scale(aXRes, aYRes);
+ transform.Translate(-quadrantTranslation);
+
+ pattern->SetMatrix(transform);
+ aTarget->SetPattern(pattern);
+
if (aOpacity != 1.0) {
aTarget->Save();
aTarget->Clip();
aTarget->Paint(aOpacity);
aTarget->Restore();
} else {
aTarget->Fill();
}
}
void
-ThebesLayerBuffer::DrawBufferWithRotation(gfxContext* aTarget, float aOpacity)
+ThebesLayerBuffer::DrawBufferWithRotation(gfxContext* aTarget, float aOpacity,
+ float aXRes, float aYRes)
{
// Draw four quadrants. We could use REPEAT_, but it's probably better
// not to, to be performance-safe.
- DrawBufferQuadrant(aTarget, LEFT, TOP, aOpacity);
- DrawBufferQuadrant(aTarget, RIGHT, TOP, aOpacity);
- DrawBufferQuadrant(aTarget, LEFT, BOTTOM, aOpacity);
- DrawBufferQuadrant(aTarget, RIGHT, BOTTOM, aOpacity);
+ DrawBufferQuadrant(aTarget, LEFT, TOP, aOpacity, aXRes, aYRes);
+ DrawBufferQuadrant(aTarget, RIGHT, TOP, aOpacity, aXRes, aYRes);
+ DrawBufferQuadrant(aTarget, LEFT, BOTTOM, aOpacity, aXRes, aYRes);
+ DrawBufferQuadrant(aTarget, RIGHT, BOTTOM, aOpacity, aXRes, aYRes);
}
static void
WrapRotationAxis(PRInt32* aRotationPoint, PRInt32 aSize)
{
if (*aRotationPoint < 0) {
*aRotationPoint += aSize;
} else if (*aRotationPoint >= aSize) {
*aRotationPoint -= aSize;
}
}
ThebesLayerBuffer::PaintState
-ThebesLayerBuffer::BeginPaint(ThebesLayer* aLayer, ContentType aContentType)
+ThebesLayerBuffer::BeginPaint(ThebesLayer* aLayer, ContentType aContentType,
+ float aXResolution, float aYResolution)
{
PaintState result;
result.mRegionToDraw.Sub(aLayer->GetVisibleRegion(), aLayer->GetValidRegion());
- if (mBuffer && aContentType != mBuffer->GetContentType()) {
+ float curXRes = aLayer->GetXResolution();
+ float curYRes = aLayer->GetYResolution();
+ if (mBuffer &&
+ (aContentType != mBuffer->GetContentType() ||
+ aXResolution != curXRes || aYResolution != curYRes)) {
// We're effectively clearing the valid region, so we need to draw
// the entire visible region now.
+ //
+ // XXX/cjones: a possibly worthwhile optimization to keep in mind
+ // is to re-use buffers when the resolution and visible region
+ // have changed in such a way that the buffer size stays the same.
+ // It might make even more sense to allocate buffers from a
+ // recyclable pool, so that we could keep this logic simple and
+ // still get back the same buffer.
result.mRegionToDraw = aLayer->GetVisibleRegion();
result.mRegionToInvalidate = aLayer->GetValidRegion();
Clear();
}
if (result.mRegionToDraw.IsEmpty())
return result;
nsIntRect drawBounds = result.mRegionToDraw.GetBounds();
nsIntRect visibleBounds = aLayer->GetVisibleRegion().GetBounds();
+ nsIntSize scaledBufferSize = ScaledSize(visibleBounds.Size(),
+ aXResolution, aYResolution);
nsRefPtr<gfxASurface> destBuffer;
nsIntRect destBufferRect;
- if (BufferSizeOkFor(visibleBounds.Size())) {
+ if (BufferSizeOkFor(scaledBufferSize)) {
+ NS_ASSERTION(curXRes == aXResolution && curYRes == aYResolution,
+ "resolution changes must Clear()!");
+
// The current buffer is big enough to hold the visible area.
if (mBufferRect.Contains(visibleBounds)) {
// We don't need to adjust mBufferRect.
destBufferRect = mBufferRect;
} else {
// The buffer's big enough but doesn't contain everything that's
// going to be visible. We'll move it.
destBufferRect = nsIntRect(visibleBounds.TopLeft(), mBufferRect.Size());
@@ -164,17 +209,19 @@ ThebesLayerBuffer::BeginPaint(ThebesLaye
// The stuff we need to redraw will wrap around an edge of the
// buffer, so we will need to do a self-copy
if (mBufferRotation == nsIntPoint(0,0)) {
destBuffer = mBuffer;
} else {
// We can't do a real self-copy because the buffer is rotated.
// So allocate a new buffer for the destination.
destBufferRect = visibleBounds;
- destBuffer = CreateBuffer(aContentType, destBufferRect.Size());
+ destBuffer = CreateBuffer(aContentType,
+ ScaledSize(destBufferRect.Size(),
+ aXResolution, aYResolution));
if (!destBuffer)
return result;
}
} else {
mBufferRect = destBufferRect;
mBufferRotation = newRotation;
}
} else {
@@ -182,33 +229,38 @@ ThebesLayerBuffer::BeginPaint(ThebesLaye
// will be redrawn, so we don't need to copy anything, so we don't
// set destBuffer.
mBufferRect = destBufferRect;
mBufferRotation = nsIntPoint(0,0);
}
} else {
// The buffer's not big enough, so allocate a new one
destBufferRect = visibleBounds;
- destBuffer = CreateBuffer(aContentType, destBufferRect.Size());
+ destBuffer = CreateBuffer(aContentType,
+ ScaledSize(destBufferRect.Size(),
+ aXResolution, aYResolution));
if (!destBuffer)
return result;
}
// If we have no buffered data already, then destBuffer will be a fresh buffer
// and we do not need to clear it below.
PRBool isClear = mBuffer == nsnull;
if (destBuffer) {
if (mBuffer) {
// Copy the bits
nsRefPtr<gfxContext> tmpCtx = new gfxContext(destBuffer);
nsIntPoint offset = -destBufferRect.TopLeft();
tmpCtx->SetOperator(gfxContext::OPERATOR_SOURCE);
+ tmpCtx->Scale(aXResolution, aYResolution);
tmpCtx->Translate(gfxPoint(offset.x, offset.y));
- DrawBufferWithRotation(tmpCtx, 1.0);
+ NS_ASSERTION(curXRes == aXResolution && curYRes == aYResolution,
+ "resolution changes must Clear()!");
+ DrawBufferWithRotation(tmpCtx, 1.0, aXResolution, aYResolution);
}
mBuffer = destBuffer.forget();
mBufferRect = destBufferRect;
mBufferRotation = nsIntPoint(0,0);
}
nsIntRegion invalidate;
@@ -219,16 +271,17 @@ ThebesLayerBuffer::BeginPaint(ThebesLaye
// Figure out which quadrant to draw in
PRInt32 xBoundary = mBufferRect.XMost() - mBufferRotation.x;
PRInt32 yBoundary = mBufferRect.YMost() - mBufferRotation.y;
XSide sideX = drawBounds.XMost() <= xBoundary ? RIGHT : LEFT;
YSide sideY = drawBounds.YMost() <= yBoundary ? BOTTOM : TOP;
nsIntRect quadrantRect = GetQuadrantRectangle(sideX, sideY);
NS_ASSERTION(quadrantRect.Contains(drawBounds), "Messed up quadrants");
+ result.mContext->Scale(aXResolution, aYResolution);
result.mContext->Translate(-gfxPoint(quadrantRect.x, quadrantRect.y));
gfxUtils::ClipToRegion(result.mContext, result.mRegionToDraw);
if (aContentType == gfxASurface::CONTENT_COLOR_ALPHA && !isClear) {
result.mContext->SetOperator(gfxContext::OPERATOR_CLEAR);
result.mContext->Paint();
result.mContext->SetOperator(gfxContext::OPERATOR_OVER);
}
--- a/gfx/layers/ThebesLayerBuffer.h
+++ b/gfx/layers/ThebesLayerBuffer.h
@@ -119,17 +119,18 @@ public:
/**
* Start a drawing operation. This returns a PaintState describing what
* needs to be drawn to bring the buffer up to date in the visible region.
* This queries aLayer to get the currently valid and visible regions.
* The returned mContext may be null if mRegionToDraw is empty.
* Otherwise it must not be null.
* mRegionToInvalidate will contain mRegionToDraw.
*/
- PaintState BeginPaint(ThebesLayer* aLayer, ContentType aContentType);
+ PaintState BeginPaint(ThebesLayer* aLayer, ContentType aContentType,
+ float aXResolution, float aYResolution);
/**
* Return a new surface of |aSize| and |aType|.
*/
virtual already_AddRefed<gfxASurface>
CreateBuffer(ContentType aType, const nsIntSize& aSize) = 0;
/**
@@ -142,18 +143,20 @@ public:
protected:
enum XSide {
LEFT, RIGHT
};
enum YSide {
TOP, BOTTOM
};
nsIntRect GetQuadrantRectangle(XSide aXSide, YSide aYSide);
- void DrawBufferQuadrant(gfxContext* aTarget, XSide aXSide, YSide aYSide, float aOpacity);
- void DrawBufferWithRotation(gfxContext* aTarget, float aOpacity);
+ void DrawBufferQuadrant(gfxContext* aTarget, XSide aXSide, YSide aYSide,
+ float aOpacity, float aXRes, float aYRes);
+ void DrawBufferWithRotation(gfxContext* aTarget, float aOpacity,
+ float aXRes, float aYRes);
const nsIntRect& BufferRect() const { return mBufferRect; }
const nsIntPoint& BufferRotation() const { return mBufferRotation; }
already_AddRefed<gfxASurface>
SetBuffer(gfxASurface* aBuffer,
const nsIntRect& aBufferRect, const nsIntPoint& aBufferRotation)
{
--- a/gfx/layers/basic/BasicLayers.cpp
+++ b/gfx/layers/basic/BasicLayers.cpp
@@ -419,29 +419,36 @@ BasicThebesLayer::Paint(gfxContext* aCon
target->PopGroupToSource();
target->Paint(aOpacity);
}
target->Restore();
return;
}
{
- Buffer::PaintState state = mBuffer.BeginPaint(this, contentType);
+ float paintXRes = BasicManager()->XResolution();
+ float paintYRes = BasicManager()->YResolution();
+ Buffer::PaintState state =
+ mBuffer.BeginPaint(this, contentType, paintXRes, paintYRes);
mValidRegion.Sub(mValidRegion, state.mRegionToInvalidate);
if (state.mContext) {
// The area that became invalid and is visible needs to be repainted
// (this could be the whole visible area if our buffer switched
// from RGB to RGBA, because we might need to repaint with
// subpixel AA)
state.mRegionToInvalidate.And(state.mRegionToInvalidate, mVisibleRegion);
InheritContextFlags(target, state.mContext);
PaintBuffer(state.mContext,
state.mRegionToDraw, state.mRegionToInvalidate,
aCallback, aCallbackData);
+
+ mXResolution = paintXRes;
+ mYResolution = paintYRes;
+ Mutated();
} else {
// It's possible that state.mRegionToInvalidate is nonempty here,
// if we are shrinking the valid region to nothing.
NS_ASSERTION(state.mRegionToDraw.IsEmpty(),
"If we need to draw, we should have a context");
}
}
@@ -454,17 +461,18 @@ BasicThebesLayerBuffer::DrawTo(ThebesLay
gfxContext* aTarget,
float aOpacity)
{
aTarget->Save();
gfxUtils::ClipToRegion(aTarget, aLayer->GetVisibleRegion());
if (aIsOpaqueContent) {
aTarget->SetOperator(gfxContext::OPERATOR_SOURCE);
}
- DrawBufferWithRotation(aTarget, aOpacity);
+ DrawBufferWithRotation(aTarget, aOpacity,
+ aLayer->GetXResolution(), aLayer->GetYResolution());
aTarget->Restore();
}
already_AddRefed<gfxASurface>
BasicThebesLayerBuffer::CreateBuffer(ContentType aType,
const nsIntSize& aSize)
{
return mLayer->CreateBuffer(aType, aSize);
--- a/gfx/layers/opengl/ThebesLayerOGL.cpp
+++ b/gfx/layers/opengl/ThebesLayerOGL.cpp
@@ -183,17 +183,17 @@ public:
NS_ABORT_IF_FALSE(mTmpSurface, "SurfaceBuffer without backing surface??");
}
virtual ~SurfaceBufferOGL() {}
// ThebesLayerBufferOGL interface
virtual PaintState BeginPaint(ContentType aContentType)
{
// Let ThebesLayerBuffer do all the hard work for us! :D
- return ThebesLayerBuffer::BeginPaint(mLayer, aContentType);
+ return ThebesLayerBuffer::BeginPaint(mLayer, aContentType, 1.0, 1.0);
}
// ThebesLayerBuffer interface
virtual already_AddRefed<gfxASurface>
CreateBuffer(ContentType aType, const nsIntSize& aSize)
{
NS_ASSERTION(gfxASurface::CONTENT_ALPHA != aType,"ThebesBuffer has color");
--- a/gfx/src/nsRect.cpp
+++ b/gfx/src/nsRect.cpp
@@ -184,22 +184,22 @@ nsMargin nsRect::operator-(const nsRect&
margin.left = aRect.x - x;
margin.right = XMost() - aRect.XMost();
margin.top = aRect.y - y;
margin.bottom = YMost() - aRect.YMost();
return margin;
}
// scale the rect but round to smallest containing rect
-nsRect& nsRect::ScaleRoundOut(float aScale)
+nsRect& nsRect::ScaleRoundOut(float aXScale, float aYScale)
{
- nscoord right = NSToCoordCeil(float(XMost()) * aScale);
- nscoord bottom = NSToCoordCeil(float(YMost()) * aScale);
- x = NSToCoordFloor(float(x) * aScale);
- y = NSToCoordFloor(float(y) * aScale);
+ nscoord right = NSToCoordCeil(float(XMost()) * aXScale);
+ nscoord bottom = NSToCoordCeil(float(YMost()) * aYScale);
+ x = NSToCoordFloor(float(x) * aXScale);
+ y = NSToCoordFloor(float(y) * aYScale);
width = (right - x);
height = (bottom - y);
return *this;
}
#ifdef DEBUG
// Diagnostics
--- a/gfx/src/nsRect.h
+++ b/gfx/src/nsRect.h
@@ -175,17 +175,18 @@ struct NS_GFX nsRect {
nsMargin operator-(const nsRect& aRect) const; // Find difference as nsMargin
nsRect& operator+=(const nsMargin& aMargin) { Inflate(aMargin); return *this; }
nsRect& operator-=(const nsMargin& aMargin) { Deflate(aMargin); return *this; }
nsRect operator+(const nsMargin& aMargin) const { return nsRect(*this) += aMargin; }
nsRect operator-(const nsMargin& aMargin) const { return nsRect(*this) -= aMargin; }
// Scale by aScale, converting coordinates to integers so that the result is
// the smallest integer-coordinate rectangle containing the unrounded result.
- nsRect& ScaleRoundOut(float aScale);
+ nsRect& ScaleRoundOut(float aScale) { return ScaleRoundOut(aScale, aScale); }
+ nsRect& ScaleRoundOut(float aXScale, float aYScale);
// Converts this rect from aFromAPP, an appunits per pixel ratio, to aToAPP.
// In the RoundOut version we make the rect the smallest rect containing the
// unrounded result. In the RoundIn version we make the rect the largest rect
// contained in the unrounded result.
inline nsRect ConvertAppUnitsRoundOut(PRInt32 aFromAPP, PRInt32 aToAPP) const;
inline nsRect ConvertAppUnitsRoundIn(PRInt32 aFromAPP, PRInt32 aToAPP) const;
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -429,16 +429,26 @@ void nsDisplayList::PaintForFrame(nsDisp
presShell->GetRootScrollFrameAsScrollable();
if (rootScrollableFrame) {
metrics.mViewportScrollOffset =
rootScrollableFrame->GetScrollPosition().ToNearestPixels(auPerCSSPixel);
}
root->SetFrameMetrics(metrics);
+ // If the layer manager supports resolution scaling, set that up
+ if (LayerManager::LAYERS_BASIC == layerManager->GetBackendType()) {
+ BasicLayerManager* basicManager =
+ static_cast<BasicLayerManager*>(layerManager.get());
+ // This is free if both resolutions are 1.0, or neither resolution
+ // has changed since the last transaction
+ basicManager->SetResolution(presShell->GetXResolution(),
+ presShell->GetYResolution());
+ }
+
layerManager->SetRoot(root);
aBuilder->LayerBuilder()->WillEndTransaction(layerManager);
layerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer,
aBuilder);
aBuilder->LayerBuilder()->DidEndTransaction(layerManager);
if (aFlags & PAINT_FLUSH_LAYERS) {
FrameLayerBuilder::InvalidateAllLayers(layerManager);