mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1697344 p1: Make GradientCache thread safe. r=jrmuizel
This is so that we can use it in the canvas worker threads. It also sets a maximum number of entries because on Windows the associated Direct2D objects can be fairly big. Differential Revision: https://phabricator.services.mozilla.com/D109790
This commit is contained in:
parent
695a760d5c
commit
3155e74038
@ -3,12 +3,15 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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/gfx/2D.h"
|
||||||
|
#include "mozilla/DataMutex.h"
|
||||||
#include "nsTArray.h"
|
#include "nsTArray.h"
|
||||||
#include "PLDHashTable.h"
|
#include "PLDHashTable.h"
|
||||||
#include "nsExpirationTracker.h"
|
#include "nsExpirationTracker.h"
|
||||||
#include "nsClassHashtable.h"
|
#include "nsClassHashtable.h"
|
||||||
#include "gfxGradientCache.h"
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
@ -93,85 +96,172 @@ struct GradientCacheData {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class implements a cache with no maximum size, that retains the
|
* This class implements a cache, that retains the GradientStops used to draw
|
||||||
* gfxPatterns used to draw the gradients.
|
* the gradients.
|
||||||
*
|
*
|
||||||
* The key is the nsStyleGradient that defines the gradient, and the size of the
|
* An entry stays in the cache as long as it is used often and we don't exceed
|
||||||
* gradient.
|
* the maximum, in which case the most recently used will be kept.
|
||||||
*
|
|
||||||
* 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 final : public nsExpirationTracker<GradientCacheData, 4> {
|
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:
|
public:
|
||||||
GradientCache()
|
GradientCache()
|
||||||
: nsExpirationTracker<GradientCacheData, 4>(MAX_GENERATION_MS,
|
: ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
|
||||||
"GradientCache") {
|
LockedInstance>(MAX_GENERATION_MS,
|
||||||
srand(time(nullptr));
|
"GradientCache") {}
|
||||||
|
static bool EnsureInstance() {
|
||||||
|
LockedInstance lockedInstance(sInstanceMutex);
|
||||||
|
return EnsureInstanceLocked(lockedInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void NotifyExpired(GradientCacheData* aObject) override {
|
static void DestroyInstance() {
|
||||||
// This will free the gfxPattern.
|
LockedInstance lockedInstance(sInstanceMutex);
|
||||||
RemoveObject(aObject);
|
if (lockedInstance) {
|
||||||
mHashEntries.Remove(aObject->mKey);
|
*lockedInstance = nullptr;
|
||||||
}
|
|
||||||
|
|
||||||
GradientCacheData* Lookup(const nsTArray<GradientStop>& aStops,
|
|
||||||
ExtendMode aExtend, BackendType aBackendType) {
|
|
||||||
GradientCacheData* gradient =
|
|
||||||
mHashEntries.Get(GradientCacheKey(aStops, aExtend, aBackendType));
|
|
||||||
|
|
||||||
if (gradient) {
|
|
||||||
MarkUsed(gradient);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return gradient;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterEntry(UniquePtr<GradientCacheData> aValue) {
|
static void AgeAllGenerations() {
|
||||||
nsresult rv = AddObject(aValue.get());
|
LockedInstance lockedInstance(sInstanceMutex);
|
||||||
if (NS_FAILED(rv)) {
|
if (!lockedInstance) {
|
||||||
// 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 thing considering we are short on memory
|
|
||||||
// anyway, we probably don't want to retain things.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mHashEntries.InsertOrUpdate(aValue->mKey, std::move(aValue));
|
lockedInstance->AgeAllGenerationsLocked(lockedInstance);
|
||||||
|
lockedInstance->NotifyHandlerEndLocked(lockedInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
static GradientCacheData* Lookup(const nsTArray<GradientStop>& aStops,
|
||||||
|
ExtendMode aExtend,
|
||||||
|
BackendType aBackendType) {
|
||||||
|
LockedInstance lockedInstance(sInstanceMutex);
|
||||||
|
if (!EnsureInstanceLocked(lockedInstance)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientCacheData* gradientData = lockedInstance->mHashEntries.Get(
|
||||||
|
GradientCacheKey(aStops, aExtend, aBackendType));
|
||||||
|
if (!gradientData) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gradientData->mStops || !gradientData->mStops->IsValid()) {
|
||||||
|
lockedInstance->NotifyExpiredLocked(gradientData, lockedInstance);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
lockedInstance->MarkUsedLocked(gradientData, lockedInstance);
|
||||||
|
return gradientData;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegisterEntry(UniquePtr<GradientCacheData> aValue) {
|
||||||
|
uint32_t numberOfEntries;
|
||||||
|
{
|
||||||
|
LockedInstance lockedInstance(sInstanceMutex);
|
||||||
|
if (!EnsureInstanceLocked(lockedInstance)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsresult rv =
|
||||||
|
lockedInstance->AddObjectLocked(aValue.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;
|
||||||
|
}
|
||||||
|
lockedInstance->mHashEntries.InsertOrUpdate(aValue->mKey,
|
||||||
|
std::move(aValue));
|
||||||
|
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);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_DispatchToCurrentThread(
|
||||||
|
NS_NewRunnableFunction("GradientCache::DestroyRemovedGradientStops",
|
||||||
|
[stops = std::move(mRemovedGradientData)] {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
static const uint32_t MAX_GENERATION_MS = 10000;
|
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.
|
* FIXME use nsTHashtable to avoid duplicating the GradientCacheKey.
|
||||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
|
* https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
|
||||||
*/
|
*/
|
||||||
nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
|
nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
|
||||||
|
nsTArray<UniquePtr<GradientCacheData>> mRemovedGradientData;
|
||||||
};
|
};
|
||||||
|
|
||||||
static GradientCache* gGradientCache = nullptr;
|
GradientCacheMutex GradientCache::sInstanceMutex("GradientCache");
|
||||||
|
|
||||||
GradientStops* gfxGradientCache::GetGradientStops(
|
void gfxGradientCache::Init() {
|
||||||
const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) {
|
MOZ_RELEASE_ASSERT(GradientCache::EnsureInstance(),
|
||||||
if (!gGradientCache) {
|
"First call must be on main thread.");
|
||||||
gGradientCache = new GradientCache();
|
|
||||||
}
|
|
||||||
GradientCacheData* cached =
|
|
||||||
gGradientCache->Lookup(aStops, aExtend, aDT->GetBackendType());
|
|
||||||
if (cached && cached->mStops) {
|
|
||||||
if (!cached->mStops->IsValid()) {
|
|
||||||
gGradientCache->NotifyExpired(cached);
|
|
||||||
} else {
|
|
||||||
return cached->mStops;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops(
|
already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops(
|
||||||
@ -181,28 +271,25 @@ already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops(
|
|||||||
aExtend);
|
aExtend);
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<GradientStops> gs = GetGradientStops(aDT, aStops, aExtend);
|
GradientCacheData* cached =
|
||||||
if (!gs) {
|
GradientCache::Lookup(aStops, aExtend, aDT->GetBackendType());
|
||||||
gs = aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend);
|
if (cached) {
|
||||||
if (!gs) {
|
return do_AddRef(cached->mStops);
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
gGradientCache->RegisterEntry(MakeUnique<GradientCacheData>(
|
|
||||||
gs, GradientCacheKey(aStops, aExtend, aDT->GetBackendType())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefPtr<GradientStops> gs =
|
||||||
|
aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend);
|
||||||
|
if (!gs) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
GradientCache::RegisterEntry(MakeUnique<GradientCacheData>(
|
||||||
|
gs, GradientCacheKey(aStops, aExtend, aDT->GetBackendType())));
|
||||||
return gs.forget();
|
return gs.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gfxGradientCache::PurgeAllCaches() {
|
void gfxGradientCache::PurgeAllCaches() { GradientCache::AgeAllGenerations(); }
|
||||||
if (gGradientCache) {
|
|
||||||
gGradientCache->AgeAllGenerations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void gfxGradientCache::Shutdown() {
|
void gfxGradientCache::Shutdown() { GradientCache::DestroyInstance(); }
|
||||||
delete gGradientCache;
|
|
||||||
gGradientCache = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace gfx
|
} // namespace gfx
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
@ -16,9 +16,7 @@ namespace gfx {
|
|||||||
|
|
||||||
class gfxGradientCache {
|
class gfxGradientCache {
|
||||||
public:
|
public:
|
||||||
static gfx::GradientStops* GetGradientStops(
|
static void Init();
|
||||||
const gfx::DrawTarget* aDT, nsTArray<gfx::GradientStop>& aStops,
|
|
||||||
gfx::ExtendMode aExtend);
|
|
||||||
|
|
||||||
static already_AddRefed<gfx::GradientStops> GetOrCreateGradientStops(
|
static already_AddRefed<gfx::GradientStops> GetOrCreateGradientStops(
|
||||||
const gfx::DrawTarget* aDT, nsTArray<gfx::GradientStop>& aStops,
|
const gfx::DrawTarget* aDT, nsTArray<gfx::GradientStop>& aStops,
|
||||||
|
@ -94,6 +94,9 @@ struct nsExpirationState {
|
|||||||
* For creating a thread-safe tracker, you can define a subclass inheriting this
|
* For creating a thread-safe tracker, you can define a subclass inheriting this
|
||||||
* base class and specialize the Mutex and AutoLock to be used.
|
* base class and specialize the Mutex and AutoLock to be used.
|
||||||
*
|
*
|
||||||
|
* For an example of using ExpirationTrackerImpl with a DataMutex
|
||||||
|
* @see mozilla::gfx::GradientCache.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
template <typename T, uint32_t K, typename Mutex, typename AutoLock>
|
template <typename T, uint32_t K, typename Mutex, typename AutoLock>
|
||||||
class ExpirationTrackerImpl {
|
class ExpirationTrackerImpl {
|
||||||
|
Loading…
Reference in New Issue
Block a user