Bug 805831: Add a cache for border corner gradients. r=joedrew

This commit is contained in:
Bas Schouten 2012-10-27 06:32:52 +02:00
parent 8b803ab863
commit e52a32f0ff
4 changed files with 421 additions and 2 deletions

View File

@ -119,6 +119,12 @@ public:
return newColor;
}
uint32_t ToABGR() const
{
return uint32_t(r * 255.0f) | uint32_t(g * 255.0f) << 8 |
uint32_t(b * 255.0f) << 16 | uint32_t(a * 255.0f) << 24;
}
Float r, g, b, a;
};

View File

@ -466,6 +466,7 @@ void nsCSSRendering::Init()
NS_ASSERTION(!gInlineBGData, "Init called twice");
gInlineBGData = new InlineBackgroundData();
gGradientCache = new GradientCache();
nsCSSBorderRenderer::Init();
}
// Clean up any global variables used by nsCSSRendering.
@ -475,6 +476,7 @@ void nsCSSRendering::Shutdown()
gInlineBGData = nullptr;
delete gGradientCache;
gGradientCache = nullptr;
nsCSSBorderRenderer::Shutdown();
}
/**

View File

@ -26,11 +26,154 @@
#include "nsINameSpaceManager.h"
#include "nsBlockFrame.h"
#include "sampler.h"
#include "nsExpirationTracker.h"
#include "gfxContext.h"
#include "nsCSSRenderingBorders.h"
#include "mozilla/gfx/2D.h"
#include "gfx2DGlue.h"
using namespace mozilla;
using namespace mozilla::gfx;
struct BorderGradientCacheKey : public PLDHashEntryHdr {
typedef const BorderGradientCacheKey& KeyType;
typedef const BorderGradientCacheKey* KeyTypePointer;
enum { ALLOW_MEMMOVE = true };
const uint32_t mColor1;
const uint32_t mColor2;
const BackendType mBackendType;
BorderGradientCacheKey(const Color& aColor1, const Color& aColor2,
BackendType aBackendType)
: mColor1(aColor1.ToABGR()), mColor2(aColor2.ToABGR())
, mBackendType(aBackendType)
{ }
BorderGradientCacheKey(const BorderGradientCacheKey* aOther)
: mColor1(aOther->mColor1), mColor2(aOther->mColor2)
, mBackendType(aOther->mBackendType)
{ }
static PLDHashNumber
HashKey(const KeyTypePointer aKey)
{
PLDHashNumber hash = 0;
hash = AddToHash(hash, aKey->mColor1);
hash = AddToHash(hash, aKey->mColor2);
hash = AddToHash(hash, aKey->mBackendType);
return hash;
}
bool KeyEquals(KeyTypePointer aKey) const
{
return (aKey->mColor1 == mColor1) &&
(aKey->mColor2 == mColor2) &&
(aKey->mBackendType == mBackendType);
}
static KeyTypePointer KeyToPointer(KeyType aKey)
{
return &aKey;
}
};
/**
* This class is what is cached. It need to be allocated in an object separated
* to the cache entry to be able to be tracked by the nsExpirationTracker.
* */
struct BorderGradientCacheData {
BorderGradientCacheData(GradientStops* aStops, const BorderGradientCacheKey& aKey)
: mStops(aStops), mKey(aKey)
{}
BorderGradientCacheData(const BorderGradientCacheData& aOther)
: mStops(aOther.mStops),
mKey(aOther.mKey)
{ }
nsExpirationState *GetExpirationState() {
return &mExpirationState;
}
nsExpirationState mExpirationState;
RefPtr<GradientStops> mStops;
BorderGradientCacheKey mKey;
};
/**
* This class implements a cache with no maximum size, that retains the
* gradient stops used to draw border corners.
*
* The key is formed by the two gradient stops, they're always both located
* at an offset of 0.5. So they can generously be reused. The key also includes
* the backend type a certain gradient was created for.
*
* An entry stays in the cache as long as it is used often.
*
* This code was pretty bluntly stolen and modified from nsCSSRendering.
*/
class BorderGradientCache MOZ_FINAL : public nsExpirationTracker<BorderGradientCacheData,4>
{
public:
BorderGradientCache()
: nsExpirationTracker<BorderGradientCacheData, 4>(GENERATION_MS)
{
mHashEntries.Init();
mTimerPeriod = GENERATION_MS;
}
virtual void NotifyExpired(BorderGradientCacheData* aObject)
{
// This will free the gfxPattern.
RemoveObject(aObject);
mHashEntries.Remove(aObject->mKey);
}
BorderGradientCacheData* Lookup(const Color& aColor1, const Color& aColor2,
BackendType aBackendType)
{
BorderGradientCacheData* gradient =
mHashEntries.Get(BorderGradientCacheKey(aColor1, aColor2, aBackendType));
if (gradient) {
MarkUsed(gradient);
}
return gradient;
}
// Returns true if we successfully register the gradient in the cache, false
// otherwise.
bool RegisterEntry(BorderGradientCacheData* aValue)
{
nsresult rv = AddObject(aValue);
if (NS_FAILED(rv)) {
// We are OOM, and we cannot track this object. We don't want stall
// entries in the hash table (since the expiration tracker is responsible
// for removing the cache entries), so we avoid putting that entry in the
// table, which is a good things considering we are short on memory
// anyway, we probably don't want to retain things.
return false;
}
mHashEntries.Put(aValue->mKey, aValue);
return true;
}
protected:
uint32_t mTimerPeriod;
static const uint32_t GENERATION_MS = 4000;
/**
* FIXME use nsTHashtable to avoid duplicating the BorderGradientCacheKey.
* This is analogous to the issue for the generic gradient cache:
* https://bugzilla.mozilla.org/show_bug.cgi?id=785794
*/
nsClassHashtable<BorderGradientCacheKey, BorderGradientCacheData> mHashEntries;
};
/**
* nsCSSRendering::PaintBorder
* nsCSSRendering::PaintOutline
@ -122,6 +265,8 @@ typedef enum {
CORNER_DOT
} CornerStyle;
static BorderGradientCache* gBorderGradientCache = nullptr;
nsCSSBorderRenderer::nsCSSBorderRenderer(int32_t aAppUnitsPerPixel,
gfxContext* aDestContext,
gfxRect& aOuterRect,
@ -162,6 +307,18 @@ nsCSSBorderRenderer::nsCSSBorderRenderer(int32_t aAppUnitsPerPixel,
mAvoidStroke = false;
}
void
nsCSSBorderRenderer::Init()
{
gBorderGradientCache = new BorderGradientCache();
}
void
nsCSSBorderRenderer::Shutdown()
{
delete gBorderGradientCache;
}
/* static */ void
nsCSSBorderRenderer::ComputeInnerRadii(const gfxCornerSizes& aRadii,
const gfxFloat *aBorderSizes,
@ -1062,7 +1219,7 @@ nsCSSBorderRenderer::CreateCornerGradient(mozilla::css::Corner aCorner,
{ -1, -1 },
{ +1, -1 },
{ +1, +1 } };
// Sides which form the 'width' and 'height' for the calculation of the angle
// for our gradient.
const int cornerWidth[4] = { 3, 1, 1, 3 };
@ -1103,6 +1260,87 @@ nsCSSBorderRenderer::CreateCornerGradient(mozilla::css::Corner aCorner,
return pattern.forget();
}
TemporaryRef<GradientStops>
nsCSSBorderRenderer::CreateCornerGradient(mozilla::css::Corner aCorner,
const gfxRGBA &aFirstColor,
const gfxRGBA &aSecondColor,
DrawTarget *aDT,
Point &aPoint1,
Point &aPoint2)
{
typedef struct { gfxFloat a, b; } twoFloats;
const twoFloats gradientCoeff[4] = { { -1, +1 },
{ -1, -1 },
{ +1, -1 },
{ +1, +1 } };
// Sides which form the 'width' and 'height' for the calculation of the angle
// for our gradient.
const int cornerWidth[4] = { 3, 1, 1, 3 };
const int cornerHeight[4] = { 0, 0, 2, 2 };
gfxPoint cornerOrigin = mOuterRect.AtCorner(aCorner);
gfxPoint pat1, pat2;
pat1.x = cornerOrigin.x +
mBorderWidths[cornerHeight[aCorner]] * gradientCoeff[aCorner].a;
pat1.y = cornerOrigin.y +
mBorderWidths[cornerWidth[aCorner]] * gradientCoeff[aCorner].b;
pat2.x = cornerOrigin.x -
mBorderWidths[cornerHeight[aCorner]] * gradientCoeff[aCorner].a;
pat2.y = cornerOrigin.y -
mBorderWidths[cornerWidth[aCorner]] * gradientCoeff[aCorner].b;
float gradientOffset = 0.25 / sqrt(pow(mBorderWidths[cornerHeight[aCorner]], 2) +
pow(mBorderWidths[cornerHeight[aCorner]], 2));
aPoint1 = Point(pat1.x, pat1.y);
aPoint2 = Point(pat2.x, pat2.y);
Color firstColor = ToColor(aFirstColor);
Color secondColor = ToColor(aSecondColor);
BorderGradientCacheData *data =
gBorderGradientCache->Lookup(firstColor, secondColor, aDT->GetType());
if (!data) {
// Having two corners, both with reversed color stops is pretty common
// for certain border types. Let's optimize it!
data = gBorderGradientCache->Lookup(secondColor, firstColor, aDT->GetType());
if (data) {
Point tmp = aPoint1;
aPoint1 = aPoint2;
aPoint2 = tmp;
}
}
RefPtr<GradientStops> stops;
if (data) {
stops = data->mStops;
} else {
GradientStop rawStops[2];
// This is only guaranteed to give correct (and in some cases more correct)
// rendering with the Direct2D Azure and Quartz Cairo backends. For other
// cairo backends it could create un-antialiased border corner transitions
// since that at least used to be pixman's behaviour for hard stops.
rawStops[0].color = firstColor;
rawStops[0].offset = 0.5;
rawStops[1].color = secondColor;
rawStops[1].offset = 0.5;
stops = aDT->CreateGradientStops(rawStops, 2);
data = new BorderGradientCacheData(stops, BorderGradientCacheKey(firstColor, secondColor, aDT->GetType()));
if (!gBorderGradientCache->RegisterEntry(data)) {
delete data;
}
}
return stops;
}
typedef struct { gfxFloat a, b; } twoFloats;
void
@ -1283,6 +1521,162 @@ nsCSSBorderRenderer::DrawNoCompositeColorSolidBorder()
}
}
void
nsCSSBorderRenderer::DrawNoCompositeColorSolidBorderAzure()
{
DrawTarget *dt = mContext->GetDrawTarget();
const gfxFloat alpha = 0.55191497064665766025;
const twoFloats cornerMults[4] = { { -1, 0 },
{ 0, -1 },
{ +1, 0 },
{ 0, +1 } };
const twoFloats centerAdjusts[4] = { { 0, +0.5 },
{ -0.5, 0 },
{ 0, -0.5 },
{ +0.5, 0 } };
Point pc, pci, p0, p1, p2, p3, pd, p3i;
gfxCornerSizes innerRadii;
ComputeInnerRadii(mBorderRadii, mBorderWidths, &innerRadii);
gfxRect strokeRect = mOuterRect;
strokeRect.Deflate(gfxMargin(mBorderWidths[3] / 2.0, mBorderWidths[0] / 2.0,
mBorderWidths[1] / 2.0, mBorderWidths[2] / 2.0));
ColorPattern colorPat(Color(0, 0, 0, 0));
LinearGradientPattern gradPat(Point(), Point(), NULL);
NS_FOR_CSS_CORNERS(i) {
// the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
mozilla::css::Corner c = mozilla::css::Corner((i+1) % 4);
mozilla::css::Corner prevCorner = mozilla::css::Corner(i);
// i+2 and i+3 respectively. These are used to index into the corner
// multiplier table, and were deduced by calculating out the long form
// of each corner and finding a pattern in the signs and values.
int i1 = (i+1) % 4;
int i2 = (i+2) % 4;
int i3 = (i+3) % 4;
pc = ToPoint(mOuterRect.AtCorner(c));
pci = ToPoint(mInnerRect.AtCorner(c));
nscolor firstColor, secondColor;
if (IsVisible(mBorderStyles[i]) && IsVisible(mBorderStyles[i1])) {
firstColor = mBorderColors[i];
secondColor = mBorderColors[i1];
} else if (IsVisible(mBorderStyles[i])) {
firstColor = mBorderColors[i];
secondColor = mBorderColors[i];
} else {
firstColor = mBorderColors[i1];
secondColor = mBorderColors[i1];
}
RefPtr<PathBuilder> builder = dt->CreatePathBuilder();
Point strokeStart, strokeEnd;
strokeStart.x = mOuterRect.AtCorner(prevCorner).x +
mBorderCornerDimensions[prevCorner].width * cornerMults[i2].a;
strokeStart.y = mOuterRect.AtCorner(prevCorner).y +
mBorderCornerDimensions[prevCorner].height * cornerMults[i2].b;
strokeEnd.x = pc.x + mBorderCornerDimensions[c].width * cornerMults[i].a;
strokeEnd.y = pc.y + mBorderCornerDimensions[c].height * cornerMults[i].b;
strokeStart.x += centerAdjusts[i].a * mBorderWidths[i];
strokeStart.y += centerAdjusts[i].b * mBorderWidths[i];
strokeEnd.x += centerAdjusts[i].a * mBorderWidths[i];
strokeEnd.y += centerAdjusts[i].b * mBorderWidths[i];
builder->MoveTo(strokeStart);
builder->LineTo(strokeEnd);
RefPtr<Path> path = builder->Finish();
dt->Stroke(path, ColorPattern(Color::FromABGR(mBorderColors[i])), StrokeOptions(mBorderWidths[i]));
Pattern *pattern;
if (firstColor != secondColor) {
gradPat.mStops = CreateCornerGradient(c, firstColor, secondColor, dt, gradPat.mBegin, gradPat.mEnd);
pattern = &gradPat;
} else {
colorPat.mColor = Color::FromABGR(firstColor);
pattern = &colorPat;
}
builder = dt->CreatePathBuilder();
if (mBorderRadii[c].width > 0 && mBorderRadii[c].height > 0) {
p0.x = pc.x + cornerMults[i].a * mBorderRadii[c].width;
p0.y = pc.y + cornerMults[i].b * mBorderRadii[c].height;
p3.x = pc.x + cornerMults[i3].a * mBorderRadii[c].width;
p3.y = pc.y + cornerMults[i3].b * mBorderRadii[c].height;
p1.x = p0.x + alpha * cornerMults[i2].a * mBorderRadii[c].width;
p1.y = p0.y + alpha * cornerMults[i2].b * mBorderRadii[c].height;
p2.x = p3.x - alpha * cornerMults[i3].a * mBorderRadii[c].width;
p2.y = p3.y - alpha * cornerMults[i3].b * mBorderRadii[c].height;
Point cornerStart;
cornerStart.x = pc.x + cornerMults[i].a * mBorderCornerDimensions[c].width;
cornerStart.y = pc.y + cornerMults[i].b * mBorderCornerDimensions[c].height;
builder->MoveTo(cornerStart);
builder->LineTo(p0);
builder->BezierTo(p1, p2, p3);
Point outerCornerEnd;
outerCornerEnd.x = pc.x + cornerMults[i3].a * mBorderCornerDimensions[c].width;
outerCornerEnd.y = pc.y + cornerMults[i3].b * mBorderCornerDimensions[c].height;
builder->LineTo(outerCornerEnd);
p0.x = pci.x + cornerMults[i].a * innerRadii[c].width;
p0.y = pci.y + cornerMults[i].b * innerRadii[c].height;
p3i.x = pci.x + cornerMults[i3].a * innerRadii[c].width;
p3i.y = pci.y + cornerMults[i3].b * innerRadii[c].height;
p1.x = p0.x + alpha * cornerMults[i2].a * innerRadii[c].width;
p1.y = p0.y + alpha * cornerMults[i2].b * innerRadii[c].height;
p2.x = p3i.x - alpha * cornerMults[i3].a * innerRadii[c].width;
p2.y = p3i.y - alpha * cornerMults[i3].b * innerRadii[c].height;
builder->LineTo(p3i);
builder->BezierTo(p2, p1, p0);
builder->Close();
path = builder->Finish();
dt->Fill(path, *pattern);
} else {
Point c1, c2, c3, c4;
c1.x = pc.x + cornerMults[i].a * mBorderCornerDimensions[c].width;
c1.y = pc.y + cornerMults[i].b * mBorderCornerDimensions[c].height;
c2 = pc;
c3.x = pc.x + cornerMults[i3].a * mBorderCornerDimensions[c].width;
c3.y = pc.y + cornerMults[i3].b * mBorderCornerDimensions[c].height;
builder->MoveTo(c1);
builder->LineTo(c2);
builder->LineTo(c3);
builder->LineTo(pci);
builder->Close();
path = builder->Finish();
dt->Fill(path, *pattern);
}
}
}
void
nsCSSBorderRenderer::DrawRectangularCompositeColors()
{
@ -1512,7 +1906,11 @@ nsCSSBorderRenderer::DrawBorders()
if (allBordersSolid && !hasCompositeColors &&
!mAvoidStroke)
{
DrawNoCompositeColorSolidBorder();
if (mContext->IsCairo()) {
DrawNoCompositeColorSolidBorder();
} else {
DrawNoCompositeColorSolidBorderAzure();
}
return;
}

View File

@ -11,6 +11,7 @@
#include "nsStyleStruct.h"
#include "gfxContext.h"
#include "mozilla/gfx/2D.h"
// define this to enable a bunch of debug dump info
#undef DEBUG_NEW_BORDERS
@ -74,6 +75,9 @@ struct nsCSSBorderRenderer {
int aSkipSides,
nscolor aBackgroundColor);
static void Init();
static void Shutdown();
gfxCornerSizes mBorderCornerDimensions;
// destination context
@ -185,12 +189,21 @@ struct nsCSSBorderRenderer {
const gfxRGBA &aFirstColor,
const gfxRGBA &aSecondColor);
// Azure variant of CreateCornerGradient.
mozilla::TemporaryRef<mozilla::gfx::GradientStops>
CreateCornerGradient(mozilla::css::Corner aCorner, const gfxRGBA &aFirstColor,
const gfxRGBA &aSecondColor, mozilla::gfx::DrawTarget *aDT,
mozilla::gfx::Point &aPoint1, mozilla::gfx::Point &aPoint2);
// Draw a solid color border that is uniformly the same width.
void DrawSingleWidthSolidBorder();
// Draw any border which is solid on all sides and does not use
// CompositeColors.
void DrawNoCompositeColorSolidBorder();
// Draw any border which is solid on all sides and does not use
// CompositeColors. Using Azure.
void DrawNoCompositeColorSolidBorderAzure();
// Draw a solid border that has no border radius (i.e. is rectangular) and
// uses CompositeColors.