зеркало из https://github.com/mozilla/gecko-dev.git
286 строки
9.7 KiB
C++
286 строки
9.7 KiB
C++
/* -*- Mode: C++; tab-width: 2; 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 "gfxGradientCache.h"
|
|
|
|
#include "MainThreadUtils.h"
|
|
#include "mozilla/gfx/2D.h"
|
|
#include "mozilla/DataMutex.h"
|
|
#include "nsTArray.h"
|
|
#include "PLDHashTable.h"
|
|
#include "nsExpirationTracker.h"
|
|
#include "nsClassHashtable.h"
|
|
#include <time.h>
|
|
|
|
namespace mozilla {
|
|
namespace gfx {
|
|
|
|
using namespace mozilla;
|
|
|
|
struct GradientCacheKey : public PLDHashEntryHdr {
|
|
typedef const GradientCacheKey& KeyType;
|
|
typedef const GradientCacheKey* KeyTypePointer;
|
|
enum { ALLOW_MEMMOVE = true };
|
|
const CopyableTArray<GradientStop> mStops;
|
|
ExtendMode mExtend;
|
|
BackendType mBackendType;
|
|
|
|
GradientCacheKey(const nsTArray<GradientStop>& aStops, ExtendMode aExtend,
|
|
BackendType aBackendType)
|
|
: mStops(aStops), mExtend(aExtend), mBackendType(aBackendType) {}
|
|
|
|
explicit GradientCacheKey(const GradientCacheKey* aOther)
|
|
: mStops(aOther->mStops),
|
|
mExtend(aOther->mExtend),
|
|
mBackendType(aOther->mBackendType) {}
|
|
|
|
GradientCacheKey(GradientCacheKey&& aOther) = default;
|
|
|
|
union FloatUint32 {
|
|
float f;
|
|
uint32_t u;
|
|
};
|
|
|
|
static PLDHashNumber HashKey(const KeyTypePointer aKey) {
|
|
PLDHashNumber hash = 0;
|
|
FloatUint32 convert;
|
|
hash = AddToHash(hash, int(aKey->mBackendType));
|
|
hash = AddToHash(hash, int(aKey->mExtend));
|
|
for (uint32_t i = 0; i < aKey->mStops.Length(); i++) {
|
|
hash = AddToHash(hash, aKey->mStops[i].color.ToABGR());
|
|
// Use the float bits as hash, except for the cases of 0.0 and -0.0 which
|
|
// both map to 0
|
|
convert.f = aKey->mStops[i].offset;
|
|
hash = AddToHash(hash, convert.f ? convert.u : 0);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
bool KeyEquals(KeyTypePointer aKey) const {
|
|
bool sameStops = true;
|
|
if (aKey->mStops.Length() != mStops.Length()) {
|
|
sameStops = false;
|
|
} else {
|
|
for (uint32_t i = 0; i < mStops.Length(); i++) {
|
|
if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() ||
|
|
mStops[i].offset != aKey->mStops[i].offset) {
|
|
sameStops = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return sameStops && (aKey->mBackendType == mBackendType) &&
|
|
(aKey->mExtend == mExtend);
|
|
}
|
|
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(GradientStops* aStops, GradientCacheKey&& aKey)
|
|
: mStops(aStops), mKey(std::move(aKey)) {}
|
|
|
|
GradientCacheData(GradientCacheData&& aOther) = default;
|
|
|
|
nsExpirationState* GetExpirationState() { return &mExpirationState; }
|
|
|
|
nsExpirationState mExpirationState;
|
|
const RefPtr<GradientStops> mStops;
|
|
GradientCacheKey mKey;
|
|
};
|
|
|
|
/**
|
|
* This class implements a cache, that retains the GradientStops used to draw
|
|
* the gradients.
|
|
*
|
|
* An entry stays in the cache as long as it is used often and we don't exceed
|
|
* the maximum, in which case the most recently used will be kept.
|
|
*/
|
|
class GradientCache;
|
|
using GradientCacheMutex = StaticDataMutex<UniquePtr<GradientCache>>;
|
|
class MOZ_RAII LockedInstance {
|
|
public:
|
|
explicit LockedInstance(GradientCacheMutex& aDataMutex)
|
|
: mAutoLock(aDataMutex.Lock()) {}
|
|
UniquePtr<GradientCache>& operator->() const& { return mAutoLock.ref(); }
|
|
UniquePtr<GradientCache>& operator->() const&& = delete;
|
|
UniquePtr<GradientCache>& operator*() const& { return mAutoLock.ref(); }
|
|
UniquePtr<GradientCache>& operator*() const&& = delete;
|
|
explicit operator bool() const { return !!mAutoLock.ref(); }
|
|
|
|
private:
|
|
GradientCacheMutex::AutoLock mAutoLock;
|
|
};
|
|
|
|
class GradientCache final
|
|
: public ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
|
|
LockedInstance> {
|
|
public:
|
|
GradientCache()
|
|
: ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
|
|
LockedInstance>(MAX_GENERATION_MS,
|
|
"GradientCache") {}
|
|
static bool EnsureInstance() {
|
|
LockedInstance lockedInstance(sInstanceMutex);
|
|
return EnsureInstanceLocked(lockedInstance);
|
|
}
|
|
|
|
static void DestroyInstance() {
|
|
LockedInstance lockedInstance(sInstanceMutex);
|
|
if (lockedInstance) {
|
|
*lockedInstance = nullptr;
|
|
}
|
|
}
|
|
|
|
static void AgeAllGenerations() {
|
|
LockedInstance lockedInstance(sInstanceMutex);
|
|
if (!lockedInstance) {
|
|
return;
|
|
}
|
|
lockedInstance->AgeAllGenerationsLocked(lockedInstance);
|
|
lockedInstance->NotifyHandlerEndLocked(lockedInstance);
|
|
}
|
|
|
|
template <typename CreateFunc>
|
|
static already_AddRefed<GradientStops> LookupOrInsert(
|
|
const GradientCacheKey& aKey, CreateFunc aCreateFunc) {
|
|
uint32_t numberOfEntries;
|
|
RefPtr<GradientStops> stops;
|
|
{
|
|
LockedInstance lockedInstance(sInstanceMutex);
|
|
if (!EnsureInstanceLocked(lockedInstance)) {
|
|
return aCreateFunc();
|
|
}
|
|
|
|
GradientCacheData* gradientData = lockedInstance->mHashEntries.Get(aKey);
|
|
if (gradientData) {
|
|
if (gradientData->mStops && gradientData->mStops->IsValid()) {
|
|
lockedInstance->MarkUsedLocked(gradientData, lockedInstance);
|
|
return do_AddRef(gradientData->mStops);
|
|
}
|
|
|
|
lockedInstance->NotifyExpiredLocked(gradientData, lockedInstance);
|
|
lockedInstance->NotifyHandlerEndLocked(lockedInstance);
|
|
}
|
|
|
|
stops = aCreateFunc();
|
|
if (!stops) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto data = MakeUnique<GradientCacheData>(stops, GradientCacheKey(&aKey));
|
|
nsresult rv = lockedInstance->AddObjectLocked(data.get(), lockedInstance);
|
|
if (NS_FAILED(rv)) {
|
|
// We are OOM, and we cannot track this object. We don't want to store
|
|
// 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 thing considering we are short on
|
|
// memory anyway, we probably don't want to retain things.
|
|
return stops.forget();
|
|
}
|
|
lockedInstance->mHashEntries.InsertOrUpdate(aKey, std::move(data));
|
|
numberOfEntries = lockedInstance->mHashEntries.Count();
|
|
}
|
|
|
|
if (numberOfEntries > MAX_ENTRIES) {
|
|
// We have too many entries force the cache to age a generation.
|
|
NS_DispatchToMainThread(
|
|
NS_NewRunnableFunction("GradientCache::OnMaxEntriesBreached", [] {
|
|
LockedInstance lockedInstance(sInstanceMutex);
|
|
if (!lockedInstance) {
|
|
return;
|
|
}
|
|
lockedInstance->AgeOneGenerationLocked(lockedInstance);
|
|
lockedInstance->NotifyHandlerEndLocked(lockedInstance);
|
|
}));
|
|
}
|
|
|
|
return stops.forget();
|
|
}
|
|
|
|
GradientCacheMutex& GetMutex() final { return sInstanceMutex; }
|
|
|
|
void NotifyExpiredLocked(GradientCacheData* aObject,
|
|
const LockedInstance& aLockedInstance) final {
|
|
// Remove the gradient from the tracker.
|
|
RemoveObjectLocked(aObject, aLockedInstance);
|
|
|
|
// If entry exists move the data to mRemovedGradientData because we want to
|
|
// drop it outside of the lock.
|
|
Maybe<UniquePtr<GradientCacheData>> gradientData =
|
|
mHashEntries.Extract(aObject->mKey);
|
|
if (gradientData.isSome()) {
|
|
mRemovedGradientData.AppendElement(std::move(*gradientData));
|
|
}
|
|
}
|
|
|
|
void NotifyHandlerEndLocked(const LockedInstance&) final {
|
|
NS_DispatchToMainThread(
|
|
NS_NewRunnableFunction("GradientCache::DestroyRemovedGradientStops",
|
|
[stops = std::move(mRemovedGradientData)] {}));
|
|
}
|
|
|
|
private:
|
|
static const uint32_t MAX_GENERATION_MS = 10000;
|
|
|
|
// On Windows some of the Direct2D objects associated with the gradient stops
|
|
// can be quite large, so we limit the number of cache entries.
|
|
static const uint32_t MAX_ENTRIES = 4000;
|
|
static GradientCacheMutex sInstanceMutex;
|
|
|
|
[[nodiscard]] static bool EnsureInstanceLocked(
|
|
LockedInstance& aLockedInstance) {
|
|
if (!aLockedInstance) {
|
|
// GradientCache must be created on the main thread.
|
|
if (!NS_IsMainThread()) {
|
|
// This should only happen at shutdown, we fall back to not caching.
|
|
return false;
|
|
}
|
|
*aLockedInstance = MakeUnique<GradientCache>();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* FIXME use nsTHashtable to avoid duplicating the GradientCacheKey.
|
|
* https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
|
|
*/
|
|
nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
|
|
nsTArray<UniquePtr<GradientCacheData>> mRemovedGradientData;
|
|
};
|
|
|
|
GradientCacheMutex GradientCache::sInstanceMutex("GradientCache");
|
|
|
|
void gfxGradientCache::Init() {
|
|
MOZ_RELEASE_ASSERT(GradientCache::EnsureInstance(),
|
|
"First call must be on main thread.");
|
|
}
|
|
|
|
already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops(
|
|
const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) {
|
|
if (aDT->IsRecording()) {
|
|
return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(),
|
|
aExtend);
|
|
}
|
|
|
|
return GradientCache::LookupOrInsert(
|
|
GradientCacheKey(aStops, aExtend, aDT->GetBackendType()),
|
|
[&]() -> already_AddRefed<GradientStops> {
|
|
return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(),
|
|
aExtend);
|
|
});
|
|
}
|
|
|
|
void gfxGradientCache::PurgeAllCaches() { GradientCache::AgeAllGenerations(); }
|
|
|
|
void gfxGradientCache::Shutdown() { GradientCache::DestroyInstance(); }
|
|
|
|
} // namespace gfx
|
|
} // namespace mozilla
|