author | Paul Adenot <paul@paul.cx> |
Sun, 26 Aug 2012 21:09:46 -0700 | |
changeset 109916 | dd28317d64d4715d9b36e5a5f0be31f4cfcef25c |
parent 109915 | 07a27119d9c983c88687f406e53ce6cabf4ae028 |
child 109917 | 1b3fa4309f3145d736a0a75762d9a1da7382d2c1 |
push id | 1708 |
push user | akeybl@mozilla.com |
push date | Mon, 19 Nov 2012 21:10:21 +0000 |
treeherder | mozilla-beta@27b14fe50103 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | dbaron |
bugs | 761393 |
milestone | 18.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -42,16 +42,17 @@ #include "nsContentUtils.h" #include "nsSVGEffects.h" #include "nsSVGIntegrationUtils.h" #include "gfxDrawable.h" #include "sampler.h" #include "nsCSSRenderingBorders.h" #include "mozilla/css/ImageLoader.h" #include "ImageContainer.h" +#include "mozilla/HashFunctions.h" using namespace mozilla; using namespace mozilla::css; // To avoid storing this data on nsInlineFrame (bloat) and to avoid // recalculating this for each frame in a continuation (perf), hold // a cache of various coordinate information that we need in order // to paint inline backgrounds. @@ -266,16 +267,158 @@ protected: // Make sure aFrame1 and aFrame2 are in the same continuation of // mBlockFrame. it1.GetContainer() == it2.GetContainer() && // And on the same line in it it1.GetLine() == it2.GetLine(); } }; +struct GradientCacheKey : public PLDHashEntryHdr { + typedef const GradientCacheKey& KeyType; + typedef const GradientCacheKey* KeyTypePointer; + enum { ALLOW_MEMMOVE = true }; + const nsRefPtr<nsStyleGradient> mGradient; + const gfxSize mGradientSize; + + GradientCacheKey(nsStyleGradient* aGradient, const gfxSize& aGradientSize) + : mGradient(aGradient), mGradientSize(aGradientSize) + { } + + GradientCacheKey(const GradientCacheKey* aOther) + : mGradient(aOther->mGradient), mGradientSize(aOther->mGradientSize) + { } + + static PLDHashNumber + HashKey(const KeyTypePointer aKey) + { + PLDHashNumber hash = 0; + hash = AddToHash(hash, aKey->mGradientSize.width); + hash = AddToHash(hash, aKey->mGradientSize.height); + hash = aKey->mGradient->Hash(hash); + return hash; + } + + bool KeyEquals(KeyTypePointer aKey) const + { + return (*aKey->mGradient == *mGradient) && + (aKey->mGradientSize == mGradientSize); + } + 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 GradientCacheData { + GradientCacheData(gfxPattern* aPattern, bool aCoversTile, GradientCacheKey aKey) + : mPattern(aPattern), mCoversTile(aCoversTile), mKey(aKey) + {} + + GradientCacheData(const GradientCacheData& aOther) + : mPattern(aOther.mPattern), + mCoversTile(aOther.mCoversTile), + mKey(aOther.mKey) + { } + + nsExpirationState *GetExpirationState() { + return &mExpirationState; + } + + nsExpirationState mExpirationState; + nsRefPtr<gfxPattern> mPattern; + bool mCoversTile; + GradientCacheKey mKey; +}; + +/** + * This class implements a cache with no maximum size, that retains the + * gfxPatterns used to draw the gradients. + * + * The key is the nsStyleGradient that defines the gradient, and the size of the + * gradient. + * + * The value is the gfxPattern, and whether or not we perform an optimization + * based on the actual gradient property. + * + * An entry stays in the cache as long as it is used often. As long as a cache + * entry is in the cache, all the references it has are guaranteed to be valid: + * the nsStyleRect for the key, the gfxPattern for the value. + */ +class GradientCache MOZ_FINAL : public nsExpirationTracker<GradientCacheData,4> +{ + public: + enum { MAX_GENERATION_MS = 10000}; + + GradientCache() + : nsExpirationTracker<GradientCacheData, 4>(MAX_GENERATION_MS) + { + mHashEntries.Init(); + } + + virtual void NotifyExpired(GradientCacheData* aObject) + { + // This will free the gfxPattern. + RemoveObject(aObject); + mHashEntries.Remove(aObject->mKey); + } + + GradientCacheData* Lookup(nsStyleGradient* aKey, const gfxSize& aGradientSize) + { + // We don't cache gradient that have Calc value, because the Calc object + // can be deallocated by the time we want to compute the hash, and thus we + // would have a dangling pointer in some nsStyleCoord in the + // nsStyleGradient that are in the hash table. + if (aKey->HasCalc()) { + return nullptr; + } + + GradientCacheData* gradient = mHashEntries.Get(GradientCacheKey(aKey, aGradientSize)); + + if (gradient) { + MarkUsed(gradient); + } + + return gradient; + } + + // Returns true if we successfully register the gradient in the cache, false + // otherwise. + bool RegisterEntry(nsStyleGradient* aKey, const gfxSize& aGradientSize, GradientCacheData* aValue) + { + // We don't cache gradient that have Calc values (see + // GradientCache::Lookup). + if (aKey->HasCalc()) { + return false; + } + 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(GradientCacheKey(aKey, aGradientSize), aValue); + return true; + } + + protected: + /** + * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey. + * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47 + */ + nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries; +}; + /* Local functions */ static void DrawBorderImage(nsPresContext* aPresContext, nsRenderingContext& aRenderingContext, nsIFrame* aForFrame, const nsRect& aBorderArea, const nsStyleBorder& aStyleBorder, const nsRect& aDirtyRect); @@ -291,29 +434,33 @@ static void DrawBorderImageComponent(nsR const nsStyleBorder& aStyleBorder, uint8_t aIndex); static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style, nscolor aBackgroundColor, nscolor aBorderColor); static InlineBackgroundData* gInlineBGData = nullptr; +static GradientCache* gGradientCache = nullptr; // Initialize any static variables used by nsCSSRendering. void nsCSSRendering::Init() { NS_ASSERTION(!gInlineBGData, "Init called twice"); gInlineBGData = new InlineBackgroundData(); + gGradientCache = new GradientCache(); } // Clean up any global variables used by nsCSSRendering. void nsCSSRendering::Shutdown() { delete gInlineBGData; gInlineBGData = nullptr; + delete gGradientCache; + gGradientCache = nullptr; } /** * Make a bevel color */ static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style, nscolor aBackgroundColor, nscolor aBorderColor) @@ -1853,251 +2000,259 @@ nsCSSRendering::PaintGradient(nsPresCont if (aOneCellArea.IsEmpty()) return; gfxContext *ctx = aRenderingContext.ThebesContext(); nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel(); gfxRect oneCellArea = nsLayoutUtils::RectToGfxRect(aOneCellArea, appUnitsPerPixel); - // Compute "gradient line" start and end relative to oneCellArea - gfxPoint lineStart, lineEnd; - double radiusX = 0, radiusY = 0; // for radial gradients only - if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) { - ComputeLinearGradientLine(aPresContext, aGradient, oneCellArea.Size(), - &lineStart, &lineEnd); - } else { - ComputeRadialGradientLine(aPresContext, aGradient, oneCellArea.Size(), - &lineStart, &lineEnd, &radiusX, &radiusY); - } - gfxFloat lineLength = NS_hypot(lineEnd.x - lineStart.x, - lineEnd.y - lineStart.y); - - NS_ABORT_IF_FALSE(aGradient->mStops.Length() >= 2, - "The parser should reject gradients with less than two stops"); - - // Build color stop array and compute stop positions - nsTArray<ColorStop> stops; - // If there is a run of stops before stop i that did not have specified - // positions, then this is the index of the first stop in that run, otherwise - // it's -1. - int32_t firstUnsetPosition = -1; - for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) { - const nsStyleGradientStop& stop = aGradient->mStops[i]; - double position; - switch (stop.mLocation.GetUnit()) { - case eStyleUnit_None: - if (i == 0) { - // First stop defaults to position 0.0 - position = 0.0; - } else if (i == aGradient->mStops.Length() - 1) { - // Last stop defaults to position 1.0 - position = 1.0; - } else { - // Other stops with no specified position get their position assigned - // later by interpolation, see below. - // Remeber where the run of stops with no specified position starts, - // if it starts here. - if (firstUnsetPosition < 0) { - firstUnsetPosition = i; - } - stops.AppendElement(ColorStop(0, stop.mColor)); - continue; - } - break; - case eStyleUnit_Percent: - position = stop.mLocation.GetPercentValue(); - break; - case eStyleUnit_Coord: - position = lineLength < 1e-6 ? 0.0 : - stop.mLocation.GetCoordValue() / appUnitsPerPixel / lineLength; - break; - default: - NS_ABORT_IF_FALSE(false, "Unknown stop position type"); - } - - if (i > 0) { - // Prevent decreasing stop positions by advancing this position - // to the previous stop position, if necessary - position = NS_MAX(position, stops[i - 1].mPosition); + bool gradientRegistered = true; + GradientCacheData* pattern = gGradientCache->Lookup(aGradient, oneCellArea.Size()); + + if (pattern == nullptr) { + // Compute "gradient line" start and end relative to oneCellArea + gfxPoint lineStart, lineEnd; + double radiusX = 0, radiusY = 0; // for radial gradients only + if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) { + ComputeLinearGradientLine(aPresContext, aGradient, oneCellArea.Size(), + &lineStart, &lineEnd); + } else { + ComputeRadialGradientLine(aPresContext, aGradient, oneCellArea.Size(), + &lineStart, &lineEnd, &radiusX, &radiusY); } - stops.AppendElement(ColorStop(position, stop.mColor)); - if (firstUnsetPosition > 0) { - // Interpolate positions for all stops that didn't have a specified position - double p = stops[firstUnsetPosition - 1].mPosition; - double d = (stops[i].mPosition - p)/(i - firstUnsetPosition + 1); - for (uint32_t j = firstUnsetPosition; j < i; ++j) { - p += d; - stops[j].mPosition = p; + gfxFloat lineLength = NS_hypot(lineEnd.x - lineStart.x, + lineEnd.y - lineStart.y); + + NS_ABORT_IF_FALSE(aGradient->mStops.Length() >= 2, + "The parser should reject gradients with less than two stops"); + + // Build color stop array and compute stop positions + nsTArray<ColorStop> stops; + // If there is a run of stops before stop i that did not have specified + // positions, then this is the index of the first stop in that run, otherwise + // it's -1. + int32_t firstUnsetPosition = -1; + for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) { + const nsStyleGradientStop& stop = aGradient->mStops[i]; + double position; + switch (stop.mLocation.GetUnit()) { + case eStyleUnit_None: + if (i == 0) { + // First stop defaults to position 0.0 + position = 0.0; + } else if (i == aGradient->mStops.Length() - 1) { + // Last stop defaults to position 1.0 + position = 1.0; + } else { + // Other stops with no specified position get their position assigned + // later by interpolation, see below. + // Remeber where the run of stops with no specified position starts, + // if it starts here. + if (firstUnsetPosition < 0) { + firstUnsetPosition = i; + } + stops.AppendElement(ColorStop(0, stop.mColor)); + continue; + } + break; + case eStyleUnit_Percent: + position = stop.mLocation.GetPercentValue(); + break; + case eStyleUnit_Coord: + position = lineLength < 1e-6 ? 0.0 : + stop.mLocation.GetCoordValue() / appUnitsPerPixel / lineLength; + break; + default: + NS_ABORT_IF_FALSE(false, "Unknown stop position type"); } - firstUnsetPosition = -1; + + if (i > 0) { + // Prevent decreasing stop positions by advancing this position + // to the previous stop position, if necessary + position = NS_MAX(position, stops[i - 1].mPosition); + } + stops.AppendElement(ColorStop(position, stop.mColor)); + if (firstUnsetPosition > 0) { + // Interpolate positions for all stops that didn't have a specified position + double p = stops[firstUnsetPosition - 1].mPosition; + double d = (stops[i].mPosition - p)/(i - firstUnsetPosition + 1); + for (uint32_t j = firstUnsetPosition; j < i; ++j) { + p += d; + stops[j].mPosition = p; + } + firstUnsetPosition = -1; + } } - } - - // Eliminate negative-position stops if the gradient is radial. - double firstStop = stops[0].mPosition; - if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && firstStop < 0.0) { - if (aGradient->mRepeating) { - // Choose an instance of the repeated pattern that gives us all positive - // stop-offsets. - double lastStop = stops[stops.Length() - 1].mPosition; - double stopDelta = lastStop - firstStop; - // If all the stops are in approximately the same place then logic below - // will kick in that makes us draw just the last stop color, so don't - // try to do anything in that case. We certainly need to avoid - // dividing by zero. - if (stopDelta >= 1e-6) { - double instanceCount = ceil(-firstStop/stopDelta); - // Advance stops by instanceCount multiples of the period of the - // repeating gradient. - double offset = instanceCount*stopDelta; - for (uint32_t i = 0; i < stops.Length(); i++) { - stops[i].mPosition += offset; + + // Eliminate negative-position stops if the gradient is radial. + double firstStop = stops[0].mPosition; + if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && firstStop < 0.0) { + if (aGradient->mRepeating) { + // Choose an instance of the repeated pattern that gives us all positive + // stop-offsets. + double lastStop = stops[stops.Length() - 1].mPosition; + double stopDelta = lastStop - firstStop; + // If all the stops are in approximately the same place then logic below + // will kick in that makes us draw just the last stop color, so don't + // try to do anything in that case. We certainly need to avoid + // dividing by zero. + if (stopDelta >= 1e-6) { + double instanceCount = ceil(-firstStop/stopDelta); + // Advance stops by instanceCount multiples of the period of the + // repeating gradient. + double offset = instanceCount*stopDelta; + for (uint32_t i = 0; i < stops.Length(); i++) { + stops[i].mPosition += offset; + } } - } - } else { - // Move negative-position stops to position 0.0. We may also need - // to set the color of the stop to the color the gradient should have - // at the center of the ellipse. - for (uint32_t i = 0; i < stops.Length(); i++) { - double pos = stops[i].mPosition; - if (pos < 0.0) { - stops[i].mPosition = 0.0; - // If this is the last stop, we don't need to adjust the color, - // it will fill the entire area. - if (i < stops.Length() - 1) { - double nextPos = stops[i + 1].mPosition; - // If nextPos is approximately equal to pos, then we don't - // need to adjust the color of this stop because it's - // not going to be displayed. - // If nextPos is negative, we don't need to adjust the color of - // this stop since it's not going to be displayed because - // nextPos will also be moved to 0.0. - if (nextPos >= 0.0 && nextPos - pos >= 1e-6) { - // Compute how far the new position 0.0 is along the interval - // between pos and nextPos. - // XXX Color interpolation (in cairo, too) should use the - // CSS 'color-interpolation' property! - double frac = (0.0 - pos)/(nextPos - pos); - stops[i].mColor = - InterpolateColor(stops[i].mColor, stops[i + 1].mColor, frac); + } else { + // Move negative-position stops to position 0.0. We may also need + // to set the color of the stop to the color the gradient should have + // at the center of the ellipse. + for (uint32_t i = 0; i < stops.Length(); i++) { + double pos = stops[i].mPosition; + if (pos < 0.0) { + stops[i].mPosition = 0.0; + // If this is the last stop, we don't need to adjust the color, + // it will fill the entire area. + if (i < stops.Length() - 1) { + double nextPos = stops[i + 1].mPosition; + // If nextPos is approximately equal to pos, then we don't + // need to adjust the color of this stop because it's + // not going to be displayed. + // If nextPos is negative, we don't need to adjust the color of + // this stop since it's not going to be displayed because + // nextPos will also be moved to 0.0. + if (nextPos >= 0.0 && nextPos - pos >= 1e-6) { + // Compute how far the new position 0.0 is along the interval + // between pos and nextPos. + // XXX Color interpolation (in cairo, too) should use the + // CSS 'color-interpolation' property! + double frac = (0.0 - pos)/(nextPos - pos); + stops[i].mColor = + InterpolateColor(stops[i].mColor, stops[i + 1].mColor, frac); + } } } } } - } - firstStop = stops[0].mPosition; - NS_ABORT_IF_FALSE(firstStop >= 0.0, "Failed to fix stop offsets"); - } - - double lastStop = stops[stops.Length() - 1].mPosition; - // Cairo gradients must have stop positions in the range [0, 1]. So, - // stop positions will be normalized below by subtracting firstStop and then - // multiplying by stopScale. - double stopScale; - double stopDelta = lastStop - firstStop; - bool zeroRadius = aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && - (radiusX < 1e-6 || radiusY < 1e-6); - if (stopDelta < 1e-6 || lineLength < 1e-6 || zeroRadius) { - // Stops are all at the same place. Map all stops to 0.0. - // For repeating radial gradients, or for any radial gradients with - // a zero radius, we need to fill with the last stop color, so just set - // both radii to 0. - stopScale = 0.0; - if (aGradient->mRepeating || zeroRadius) { - radiusX = radiusY = 0.0; + firstStop = stops[0].mPosition; + NS_ABORT_IF_FALSE(firstStop >= 0.0, "Failed to fix stop offsets"); } - lastStop = firstStop; - } else { - stopScale = 1.0/stopDelta; - } - - // Create the gradient pattern. - nsRefPtr<gfxPattern> gradientPattern; - bool forceRepeatToCoverTiles = false; - if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) { - // Compute the actual gradient line ends we need to pass to cairo after - // stops have been normalized. - gfxPoint gradientStart = lineStart + (lineEnd - lineStart)*firstStop; - gfxPoint gradientEnd = lineStart + (lineEnd - lineStart)*lastStop; - - if (stopScale == 0.0) { - // Stops are all at the same place. For repeating gradients, this will - // just paint the last stop color. We don't need to do anything. - // For non-repeating gradients, this should render as two colors, one - // on each "side" of the gradient line segment, which is a point. All - // our stops will be at 0.0; we just need to set the direction vector - // correctly. - gradientEnd = gradientStart + (lineEnd - lineStart); + + double lastStop = stops[stops.Length() - 1].mPosition; + // Cairo gradients must have stop positions in the range [0, 1]. So, + // stop positions will be normalized below by subtracting firstStop and then + // multiplying by stopScale. + double stopScale; + double stopDelta = lastStop - firstStop; + bool zeroRadius = aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && + (radiusX < 1e-6 || radiusY < 1e-6); + if (stopDelta < 1e-6 || lineLength < 1e-6 || zeroRadius) { + // Stops are all at the same place. Map all stops to 0.0. + // For repeating radial gradients, or for any radial gradients with + // a zero radius, we need to fill with the last stop color, so just set + // both radii to 0. + stopScale = 0.0; + if (aGradient->mRepeating || zeroRadius) { + radiusX = radiusY = 0.0; + } + lastStop = firstStop; + } else { + stopScale = 1.0/stopDelta; } - gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y, - gradientEnd.x, gradientEnd.y); - - // When the gradient line is parallel to the x axis from the left edge - // to the right edge of a tile, then we can repeat by just repeating the - // gradient. - if ((gradientStart.y == gradientEnd.y && gradientStart.x == 0 && - gradientEnd.x == oneCellArea.width) || - (gradientStart.x == gradientEnd.x && gradientStart.y == 0 && - gradientEnd.y == oneCellArea.height)) { - forceRepeatToCoverTiles = true; - } - } else { - NS_ASSERTION(firstStop >= 0.0, - "Negative stops not allowed for radial gradients"); - - // To form an ellipse, we'll stretch a circle vertically, if necessary. - // So our radii are based on radiusX. - double innerRadius = radiusX*firstStop; - double outerRadius = radiusX*lastStop; - if (stopScale == 0.0) { - // Stops are all at the same place. See above (except we now have - // the inside vs. outside of an ellipse). - outerRadius = innerRadius + 1; + // Create the gradient pattern. + nsRefPtr<gfxPattern> gradientPattern; + bool forceRepeatToCoverTiles = false; + if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) { + // Compute the actual gradient line ends we need to pass to cairo after + // stops have been normalized. + gfxPoint gradientStart = lineStart + (lineEnd - lineStart)*firstStop; + gfxPoint gradientEnd = lineStart + (lineEnd - lineStart)*lastStop; + + if (stopScale == 0.0) { + // Stops are all at the same place. For repeating gradients, this will + // just paint the last stop color. We don't need to do anything. + // For non-repeating gradients, this should render as two colors, one + // on each "side" of the gradient line segment, which is a point. All + // our stops will be at 0.0; we just need to set the direction vector + // correctly. + gradientEnd = gradientStart + (lineEnd - lineStart); + } + + gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y, + gradientEnd.x, gradientEnd.y); + + // When the gradient line is parallel to the x axis from the left edge + // to the right edge of a tile, then we can repeat by just repeating the + // gradient. + if ((gradientStart.y == gradientEnd.y && gradientStart.x == 0 && + gradientEnd.x == oneCellArea.width) || + (gradientStart.x == gradientEnd.x && gradientStart.y == 0 && + gradientEnd.y == oneCellArea.height)) { + forceRepeatToCoverTiles = true; + } + } else { + NS_ASSERTION(firstStop >= 0.0, + "Negative stops not allowed for radial gradients"); + + // To form an ellipse, we'll stretch a circle vertically, if necessary. + // So our radii are based on radiusX. + double innerRadius = radiusX*firstStop; + double outerRadius = radiusX*lastStop; + if (stopScale == 0.0) { + // Stops are all at the same place. See above (except we now have + // the inside vs. outside of an ellipse). + outerRadius = innerRadius + 1; + } + gradientPattern = new gfxPattern(lineStart.x, lineStart.y, innerRadius, + lineStart.x, lineStart.y, outerRadius); + if (radiusX != radiusY) { + // Stretch the circles into ellipses vertically by setting a transform + // in the pattern. + // Recall that this is the transform from user space to pattern space. + // So to stretch the ellipse by factor of P vertically, we scale + // user coordinates by 1/P. + gfxMatrix matrix; + matrix.Translate(lineStart); + matrix.Scale(1.0, radiusX/radiusY); + matrix.Translate(-lineStart); + gradientPattern->SetMatrix(matrix); + } } - gradientPattern = new gfxPattern(lineStart.x, lineStart.y, innerRadius, - lineStart.x, lineStart.y, outerRadius); - if (radiusX != radiusY) { - // Stretch the circles into ellipses vertically by setting a transform - // in the pattern. - // Recall that this is the transform from user space to pattern space. - // So to stretch the ellipse by factor of P vertically, we scale - // user coordinates by 1/P. - gfxMatrix matrix; - matrix.Translate(lineStart); - matrix.Scale(1.0, radiusX/radiusY); - matrix.Translate(-lineStart); - gradientPattern->SetMatrix(matrix); + if (gradientPattern->CairoStatus()) + return; + + // Now set normalized color stops in pattern. + if (stopScale == 0.0) { + // Non-repeating gradient with all stops in same place -> just add + // first stop and last stop, both at position 0. + // Repeating gradient with all stops in the same place, or radial + // gradient with radius of 0 -> just paint the last stop color. + if (!aGradient->mRepeating && !zeroRadius) { + gradientPattern->AddColorStop(0.0, stops[0].mColor); + } + gradientPattern->AddColorStop(0.0, stops[stops.Length() - 1].mColor); + } else { + // Use all stops + for (uint32_t i = 0; i < stops.Length(); i++) { + double pos = stopScale*(stops[i].mPosition - firstStop); + gradientPattern->AddColorStop(pos, stops[i].mColor); + } } - } - if (gradientPattern->CairoStatus()) - return; - - // Now set normalized color stops in pattern. - if (stopScale == 0.0) { - // Non-repeating gradient with all stops in same place -> just add - // first stop and last stop, both at position 0. - // Repeating gradient with all stops in the same place, or radial - // gradient with radius of 0 -> just paint the last stop color. - if (!aGradient->mRepeating && !zeroRadius) { - gradientPattern->AddColorStop(0.0, stops[0].mColor); + + // Set repeat mode. Default cairo extend mode is PAD. + if (aGradient->mRepeating || forceRepeatToCoverTiles) { + gradientPattern->SetExtend(gfxPattern::EXTEND_REPEAT); } - gradientPattern->AddColorStop(0.0, stops[stops.Length() - 1].mColor); - } else { - // Use all stops - for (uint32_t i = 0; i < stops.Length(); i++) { - double pos = stopScale*(stops[i].mPosition - firstStop); - gradientPattern->AddColorStop(pos, stops[i].mColor); - } - } - - // Set repeat mode. Default cairo extend mode is PAD. - if (aGradient->mRepeating || forceRepeatToCoverTiles) { - gradientPattern->SetExtend(gfxPattern::EXTEND_REPEAT); + // Register the gradient newly computed in the cache. + pattern = new GradientCacheData(gradientPattern, forceRepeatToCoverTiles, GradientCacheKey(aGradient, oneCellArea.Size())); + gradientRegistered = gGradientCache->RegisterEntry(aGradient, oneCellArea.Size(), pattern); } // Paint gradient tiles. This isn't terribly efficient, but doing it this // way is simple and sure to get pixel-snapping right. We could speed things // up by drawing tiles into temporary surfaces and copying those to the // destination, but after pixel-snapping tiles may not all be the same size. nsRect dirty; if (!dirty.IntersectRect(aDirtyRect, aFillArea)) @@ -2105,30 +2260,30 @@ nsCSSRendering::PaintGradient(nsPresCont gfxRect areaToFill = nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerPixel); gfxMatrix ctm = ctx->CurrentMatrix(); // xStart/yStart are the top-left corner of the top-left tile. nscoord xStart = FindTileStart(dirty.x, aOneCellArea.x, aOneCellArea.width); nscoord yStart = FindTileStart(dirty.y, aOneCellArea.y, aOneCellArea.height); - nscoord xEnd = forceRepeatToCoverTiles ? xStart + aOneCellArea.width : dirty.XMost(); - nscoord yEnd = forceRepeatToCoverTiles ? yStart + aOneCellArea.height : dirty.YMost(); + nscoord xEnd = pattern->mCoversTile ? xStart + aOneCellArea.width : dirty.XMost(); + nscoord yEnd = pattern->mCoversTile ? yStart + aOneCellArea.height : dirty.YMost(); // x and y are the top-left corner of the tile to draw for (nscoord y = yStart; y < yEnd; y += aOneCellArea.height) { for (nscoord x = xStart; x < xEnd; x += aOneCellArea.width) { // The coordinates of the tile gfxRect tileRect = nsLayoutUtils::RectToGfxRect( nsRect(x, y, aOneCellArea.width, aOneCellArea.height), appUnitsPerPixel); // The actual area to fill with this tile is the intersection of this // tile with the overall area we're supposed to be filling gfxRect fillRect = - forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill); + pattern->mCoversTile ? areaToFill : tileRect.Intersect(areaToFill); ctx->NewPath(); // If we can snap the gradient tile and fill rects, do so, but make sure // that the gradient is scaled precisely to the tile rect. gfxRect fillRectSnapped = fillRect; // Don't snap the tileRect directly since that would lose information // about the orientation of the current transform (i.e. vertical or // horizontal flipping). Instead snap the corners independently so if // the CTM has a flip, our Scale() below preserves the flip. @@ -2141,21 +2296,26 @@ nsCSSRendering::PaintGradient(nsPresCont ctx->Rectangle(fillRectSnapped); ctx->Translate(tileRectSnappedTopLeft); ctx->Scale((tileRectSnappedBottomRight.x - tileRectSnappedTopLeft.x)/tileRect.width, (tileRectSnappedBottomRight.y - tileRectSnappedTopLeft.y)/tileRect.height); } else { ctx->Rectangle(fillRect); ctx->Translate(tileRect.TopLeft()); } - ctx->SetPattern(gradientPattern); + ctx->SetPattern(pattern->mPattern); ctx->Fill(); ctx->SetMatrix(ctm); } } + // If we could not put the gradient in the gradient cache, make sure to + // release its resources so we don't leak. + if (!gradientRegistered) { + delete pattern; + } } void nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext, nsRenderingContext& aRenderingContext, nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
--- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1443,16 +1443,43 @@ nsStyleGradient::IsOpaque() { for (uint32_t i = 0; i < mStops.Length(); i++) { if (NS_GET_A(mStops[i].mColor) < 255) return false; } return true; } +bool +nsStyleGradient::HasCalc() +{ + // The stops cannot have a Calc. + return mBgPosX.IsCalcUnit() || mBgPosY.IsCalcUnit() || mAngle.IsCalcUnit() || + mRadiusX.IsCalcUnit() || mRadiusX.IsCalcUnit(); +} + +uint32_t +nsStyleGradient::Hash(PLDHashNumber aHash) +{ + aHash = mozilla::AddToHash(aHash, mShape); + aHash = mozilla::AddToHash(aHash, mSize); + aHash = mozilla::AddToHash(aHash, mRepeating); + aHash = mozilla::AddToHash(aHash, mLegacySyntax); + aHash = mBgPosX.HashValue(aHash); + aHash = mBgPosY.HashValue(aHash); + aHash = mAngle.HashValue(aHash); + aHash = mRadiusX.HashValue(aHash); + aHash = mRadiusY.HashValue(aHash); + for (uint32_t i = 0; i < mStops.Length(); i++) { + aHash = mStops[i].mLocation.HashValue(aHash); + aHash = mozilla::AddToHash(aHash, mStops[i].mColor); + } + return aHash; +} + // -------------------- // nsStyleImage // nsStyleImage::nsStyleImage() : mType(eStyleImageType_Null) , mCropRect(nullptr) #ifdef DEBUG
--- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -144,16 +144,18 @@ public: nsTArray<nsStyleGradientStop> mStops; bool operator==(const nsStyleGradient& aOther) const; bool operator!=(const nsStyleGradient& aOther) const { return !(*this == aOther); } bool IsOpaque(); + bool HasCalc(); + uint32_t Hash(PLDHashNumber aHash); NS_INLINE_DECL_REFCOUNTING(nsStyleGradient) private: ~nsStyleGradient() {} nsStyleGradient(const nsStyleGradient& aOther) MOZ_DELETE; nsStyleGradient& operator=(const nsStyleGradient& aOther) MOZ_DELETE;