Bug 1624125 - Track display list changes in DisplayItemCache r=jrmuizel

This is needed because display lists and DisplayItemCache have different lifetimes. For example, display lists can outlive WebRenderLayerManager when device reset occurs.

A slightly nicer way of fixing this would be to couple DisplayItemCache with nsDisplayList or nsDisplayListBuilder. This is would currently require a lot of refactoring to look nice, because the painting code still supports non-retained display lists and non-WR code paths.

Differential Revision: https://phabricator.services.mozilla.com/D68193

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Miko Mynttinen 2020-03-27 16:49:37 +00:00
Родитель 16396717d4
Коммит 9b727b1e0b
5 изменённых файлов: 124 добавлений и 92 удалений

Просмотреть файл

@ -5,20 +5,79 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DisplayItemCache.h"
#include "nsDisplayList.h"
namespace mozilla {
namespace layers {
static const size_t kInitialCacheSize = 1024;
static const size_t kMaximumCacheSize = 10240;
static const size_t kCacheThreshold = 1;
DisplayItemCache::DisplayItemCache()
: mMaximumSize(0), mConsecutivePartialDisplayLists(0) {
if (XRE_IsContentProcess() &&
StaticPrefs::gfx_webrender_enable_item_cache_AtStartup()) {
SetCapacity(kInitialCacheSize, kMaximumCacheSize);
: mDisplayList(nullptr),
mMaximumSize(0),
mPipelineId{},
mCaching(false),
mInvalid(false) {}
void DisplayItemCache::SetDisplayList(nsDisplayListBuilder* aBuilder,
nsDisplayList* aList) {
if (!IsEnabled()) {
return;
}
MOZ_ASSERT(aBuilder);
MOZ_ASSERT(aList);
const bool listChanged = mDisplayList != aList;
const bool partialBuild = !aBuilder->PartialBuildFailed();
if (listChanged && partialBuild) {
// If the display list changed during a partial update, it means that
// |SetDisplayList()| has missed one rebuilt display list.
mDisplayList = nullptr;
return;
}
if (listChanged || !partialBuild) {
// The display list has been changed or rebuilt.
mDisplayList = aList;
mInvalid = true;
}
UpdateState();
}
void DisplayItemCache::SetPipelineId(const wr::PipelineId& aPipelineId) {
mInvalid = mInvalid || !(mPipelineId == aPipelineId);
mPipelineId = aPipelineId;
}
void DisplayItemCache::UpdateState() {
// |mCaching == true| if:
// 1) |SetDisplayList()| is called with a fully rebuilt display list
// followed by
// 2a) |SetDisplayList()| is called with a partially updated display list
// or
// 2b) |SkipWaitingForPartialDisplayList()| is called
mCaching = !mInvalid;
#if 0
Stats().Print();
Stats().Reset();
#endif
if (IsEmpty()) {
// The cache is empty so nothing needs to be updated.
mInvalid = false;
return;
}
// Clear the cache if the current state is invalid.
if (mInvalid) {
ClearCache();
} else {
FreeUnusedSlots();
}
mInvalid = false;
}
void DisplayItemCache::ClearCache() {
@ -77,20 +136,12 @@ void DisplayItemCache::SetCapacity(const size_t aInitialSize,
}
Maybe<uint16_t> DisplayItemCache::AssignSlot(nsPaintedDisplayItem* aItem) {
if (kCacheThreshold > mConsecutivePartialDisplayLists) {
// Wait for |kCacheThreshold| partial display list builds, before caching
// display items. This is meant to avoid caching overhead for interactions
// or pages that do not work well with retained display lists.
// TODO: This is a speculative optimization to minimize regressions.
return Nothing();
}
if (!aItem->CanBeReused() || !aItem->CanBeCached()) {
// Do not try to cache items that cannot be reused.
if (!mCaching || !aItem->CanBeReused() || !aItem->CanBeCached()) {
return Nothing();
}
auto& slot = aItem->CacheIndex();
if (!slot) {
slot = GetNextFreeSlot();
if (!slot) {
@ -142,27 +193,5 @@ Maybe<uint16_t> DisplayItemCache::CanReuseItem(
return slotIndex;
}
void DisplayItemCache::UpdateState(const bool aPartialDisplayListBuildFailed,
const wr::PipelineId& aPipelineId) {
const bool pipelineIdChanged = UpdatePipelineId(aPipelineId);
const bool invalidate = pipelineIdChanged || aPartialDisplayListBuildFailed;
mConsecutivePartialDisplayLists =
invalidate ? 0 : mConsecutivePartialDisplayLists + 1;
if (IsEmpty()) {
// The cache is empty so nothing needs to be updated.
return;
}
// Clear the cache if the partial display list build failed, or if the
// pipeline id changed.
if (invalidate) {
ClearCache();
} else {
FreeUnusedSlots();
}
}
} // namespace layers
} // namespace mozilla

Просмотреть файл

@ -8,9 +8,10 @@
#define GFX_DISPLAY_ITEM_CACHE_H
#include "mozilla/webrender/WebRenderAPI.h"
#include "mozilla/Maybe.h"
#include "nsTArray.h"
class nsDisplayList;
class nsDisplayListBuilder;
class nsPaintedDisplayItem;
namespace mozilla {
@ -64,6 +65,28 @@ class DisplayItemCache final {
public:
DisplayItemCache();
/**
* Sets the initial and max cache size to given |aInitialSize| and |aMaxSize|.
*/
void SetCapacity(const size_t aInitialSize, const size_t aMaximumSize);
/**
* Sets the display list used by the cache.
*/
void SetDisplayList(nsDisplayListBuilder* aBuilder, nsDisplayList* aList);
/**
* Sets the pipeline id used by the cache.
*/
void SetPipelineId(const wr::PipelineId& aPipelineId);
/**
* Enables caching immediately if the cache is valid, and display list is set.
*/
void SkipWaitingForPartialDisplayList() {
mCaching = mDisplayList && !mInvalid;
}
/**
* Returns true if display item caching is enabled, otherwise false.
*/
@ -81,16 +104,6 @@ class DisplayItemCache final {
return mFreeSlots.IsEmpty() && CurrentSize() == mMaximumSize;
}
/**
* Updates the cache state based on the given display list build information
* and pipeline id.
*
* This is necessary because Gecko display items can only be reused for the
* partial display list builds following a full display list build.
*/
void UpdateState(const bool aPartialDisplayListBuildFailed,
const wr::PipelineId& aPipelineId);
/**
* Returns the current cache size.
*/
@ -131,29 +144,23 @@ class DisplayItemCache final {
void ClearCache();
void FreeUnusedSlots();
bool GrowIfPossible();
Maybe<uint16_t> GetNextFreeSlot();
bool GrowIfPossible();
void UpdateState();
/**
* Sets the initial and max cache size to given |aInitialSize| and |aMaxSize|.
*/
void SetCapacity(const size_t aInitialSize, const size_t aMaximumSize);
/**
* Returns true if the given |aPipelineId| is different from the previous one,
* otherwise returns false.
*/
bool UpdatePipelineId(const wr::PipelineId& aPipelineId) {
const bool isSame = mPreviousPipelineId.refOr(aPipelineId) == aPipelineId;
mPreviousPipelineId = Some(aPipelineId);
return !isSame;
}
// The lifetime of display lists exceed the lifetime of DisplayItemCache.
// This pointer stores the address of the display list that is using this
// cache, and it is only used for pointer comparisons.
nsDisplayList* mDisplayList;
size_t mMaximumSize;
nsTArray<Slot> mSlots;
nsTArray<uint16_t> mFreeSlots;
Maybe<wr::PipelineId> mPreviousPipelineId;
size_t mConsecutivePartialDisplayLists;
wr::PipelineId mPipelineId;
bool mCaching;
bool mInvalid;
CacheStats mCacheStats;
};

Просмотреть файл

@ -46,6 +46,14 @@ WebRenderLayerManager::WebRenderLayerManager(nsIWidget* aWidget)
mStateManagers[renderRoot].mRenderRoot = renderRoot;
mStateManagers[renderRoot].mLayerManager = this;
}
if (XRE_IsContentProcess() &&
StaticPrefs::gfx_webrender_enable_item_cache_AtStartup()) {
static const size_t kInitialCacheSize = 1024;
static const size_t kMaximumCacheSize = 10240;
mDisplayItemCache.SetCapacity(kInitialCacheSize, kMaximumCacheSize);
}
}
KnowsCompositor* WebRenderLayerManager::AsKnowsCompositor() { return mWrChild; }
@ -198,6 +206,8 @@ bool WebRenderLayerManager::EndEmptyTransaction(EndTransactionFlags aFlags) {
return false;
}
mDisplayItemCache.SkipWaitingForPartialDisplayList();
// Since we don't do repeat transactions right now, just set the time
mAnimationReadyTime = TimeStamp::Now();
@ -347,14 +357,12 @@ void WebRenderLayerManager::EndTransactionWithoutLayer(
// generating the WR display list is the closest equivalent
PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization);
builder.UpdateCacheState(aDisplayListBuilder->PartialBuildFailed());
mDisplayItemCache.SetDisplayList(aDisplayListBuilder, aDisplayList);
mWebRenderCommandBuilder.BuildWebRenderCommands(
builder, resourceUpdates, aDisplayList, aDisplayListBuilder,
mScrollDatas, std::move(aFilters));
builder.UpdateCacheSize();
builderDumpIndex =
mWebRenderCommandBuilder.GetBuilderDumpIndex(builder.GetRenderRoot());
containsSVGGroup = mWebRenderCommandBuilder.GetContainsSVGGroup();

Просмотреть файл

@ -912,6 +912,10 @@ DisplayListBuilder::DisplayListBuilder(PipelineId aId,
mDisplayItemCache(aCache) {
MOZ_COUNT_CTOR(DisplayListBuilder);
mWrState = wr_state_new(aId, aContentSize, aCapacity);
if (mDisplayItemCache && mDisplayItemCache->IsEnabled()) {
mDisplayItemCache->SetPipelineId(aId);
}
}
DisplayListBuilder::~DisplayListBuilder() {
@ -967,6 +971,11 @@ void DisplayListBuilder::Finalize(wr::LayoutSize& aOutContentSize,
void DisplayListBuilder::Finalize(
layers::RenderRootDisplayListData& aOutTransaction) {
MOZ_ASSERT(mRenderRoot == wr::RenderRoot::Default);
if (mDisplayItemCache && mDisplayItemCache->IsEnabled()) {
wr_dp_set_cache_size(mWrState, mDisplayItemCache->CurrentSize());
}
wr::VecU8 dl;
wr_api_finalize_builder(SubBuilder(aOutTransaction.mRenderRoot).mWrState,
&aOutTransaction.mContentSize,
@ -1529,24 +1538,6 @@ bool DisplayListBuilder::ReuseItem(nsPaintedDisplayItem* aItem) {
return false;
}
void DisplayListBuilder::UpdateCacheState(
const bool aPartialDisplayListBuildFailed) {
if (mDisplayItemCache && mDisplayItemCache->IsEnabled()) {
mDisplayItemCache->UpdateState(aPartialDisplayListBuildFailed,
CurrentPipelineId());
#if 0
mDisplayItemCache->Stats().Print();
mDisplayItemCache->Stats().Reset();
#endif
}
}
void DisplayListBuilder::UpdateCacheSize() {
if (mDisplayItemCache && mDisplayItemCache->IsEnabled()) {
wr_dp_set_cache_size(mWrState, mDisplayItemCache->CurrentSize());
}
}
Maybe<layers::ScrollableLayerGuid::ViewID>
DisplayListBuilder::GetContainingFixedPosScrollTarget(
const ActiveScrolledRoot* aAsr) {

Просмотреть файл

@ -641,9 +641,6 @@ class DisplayListBuilder final {
*/
bool ReuseItem(nsPaintedDisplayItem* aItem);
void UpdateCacheState(const bool aPartialDisplayListBuildFailed);
void UpdateCacheSize();
uint64_t CurrentClipChainId() const {
return mCurrentSpaceAndClipChain.clip_chain;
}