blob: 9b2140e2a59e41bdc5664144d2243c98be6e0024 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/graphics/paint/PaintController.h"
#include "platform/TraceEvent.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/graphics/paint/DrawingDisplayItem.h"
#include "third_party/skia/include/core/SkPictureAnalyzer.h"
#include "wtf/text/StringBuilder.h"
#ifndef NDEBUG
#include "platform/graphics/LoggingCanvas.h"
#include <stdio.h>
#endif
namespace blink {
const PaintArtifact& PaintController::paintArtifact() const
{
DCHECK(m_newDisplayItemList.isEmpty());
DCHECK(m_newPaintChunks.isInInitialState());
return m_currentPaintArtifact;
}
bool PaintController::useCachedDrawingIfPossible(const DisplayItemClient& client, DisplayItem::Type type)
{
DCHECK(DisplayItem::isDrawingType(type));
if (displayItemConstructionIsDisabled())
return false;
if (!clientCacheIsValid(client))
return false;
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled() && isCheckingUnderInvalidation()) {
// We are checking under-invalidation of a subsequence enclosing this display item.
// Let the client continue to actually paint the display item.
return false;
}
size_t cachedItem = findCachedItem(DisplayItem::Id(client, type));
if (cachedItem == kNotFound) {
NOTREACHED();
return false;
}
++m_numCachedNewItems;
ensureNewDisplayItemListInitialCapacity();
if (!RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled())
processNewItem(m_newDisplayItemList.appendByMoving(m_currentPaintArtifact.getDisplayItemList()[cachedItem]), FromCachedItem);
m_nextItemToMatch = cachedItem + 1;
// Items before m_nextItemToMatch have been copied so we don't need to index them.
if (m_nextItemToMatch > m_nextItemToIndex)
m_nextItemToIndex = m_nextItemToMatch;
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled()) {
if (!isCheckingUnderInvalidation()) {
m_underInvalidationCheckingBegin = cachedItem;
m_underInvalidationCheckingEnd = cachedItem + 1;
m_underInvalidationMessagePrefix = "";
}
// Return false to let the painter actually paint, and we will check if the new painting
// is the same as the cached.
return false;
}
return true;
}
bool PaintController::useCachedSubsequenceIfPossible(const DisplayItemClient& client)
{
if (displayItemConstructionIsDisabled() || subsequenceCachingIsDisabled())
return false;
if (!clientCacheIsValid(client))
return false;
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled() && isCheckingUnderInvalidation()) {
// We are checking under-invalidation of an ancestor subsequence enclosing this one.
// The ancestor subsequence is supposed to have already "copied", so we should let the
// client continue to actually paint the descendant subsequences without "copying".
return false;
}
size_t cachedItem = findCachedItem(DisplayItem::Id(client, DisplayItem::kSubsequence));
if (cachedItem == kNotFound) {
NOTREACHED();
return false;
}
// |cachedItem| will point to the first item after the subsequence or end of the current list.
ensureNewDisplayItemListInitialCapacity();
copyCachedSubsequence(cachedItem);
m_nextItemToMatch = cachedItem;
// Items before |cachedItem| have been copied so we don't need to index them.
if (cachedItem > m_nextItemToIndex)
m_nextItemToIndex = cachedItem;
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled()) {
// Return false to let the painter actually paint, and we will check if the new painting
// is the same as the cached.
return false;
}
return true;
}
bool PaintController::lastDisplayItemIsNoopBegin() const
{
if (m_newDisplayItemList.isEmpty())
return false;
const auto& lastDisplayItem = m_newDisplayItemList.last();
return lastDisplayItem.isBegin() && !lastDisplayItem.drawsContent();
}
void PaintController::removeLastDisplayItem()
{
if (m_newDisplayItemList.isEmpty())
return;
#if DCHECK_IS_ON()
// Also remove the index pointing to the removed display item.
IndicesByClientMap::iterator it = m_newDisplayItemIndicesByClient.find(&m_newDisplayItemList.last().client());
if (it != m_newDisplayItemIndicesByClient.end()) {
Vector<size_t>& indices = it->value;
if (!indices.isEmpty() && indices.last() == (m_newDisplayItemList.size() - 1))
indices.removeLast();
}
#endif
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled() && isCheckingUnderInvalidation()) {
if (m_skippedProbableUnderInvalidationCount) {
--m_skippedProbableUnderInvalidationCount;
} else {
DCHECK(m_underInvalidationCheckingBegin);
--m_underInvalidationCheckingBegin;
}
}
m_newDisplayItemList.removeLast();
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
m_newPaintChunks.decrementDisplayItemIndex();
}
const DisplayItem* PaintController::lastDisplayItem(unsigned offset)
{
if (offset < m_newDisplayItemList.size())
return &m_newDisplayItemList[m_newDisplayItemList.size() - offset - 1];
return nullptr;
}
void PaintController::processNewItem(DisplayItem& displayItem, NewItemSource newItemSource)
{
DCHECK(!m_constructionDisabled);
#if CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS
if (!isSkippingCache()) {
if (displayItem.isCacheable()) {
// Mark the client shouldKeepAlive under this PaintController.
// The status will end after the new display items are committed.
displayItem.client().beginShouldKeepAlive(this);
if (!m_currentSubsequenceClients.isEmpty()) {
// Mark the client shouldKeepAlive under the current subsequence.
// The status will end when the subsequence owner is invalidated or deleted.
displayItem.client().beginShouldKeepAlive(m_currentSubsequenceClients.last());
}
}
if (displayItem.getType() == DisplayItem::kSubsequence) {
m_currentSubsequenceClients.append(&displayItem.client());
} else if (displayItem.getType() == DisplayItem::kEndSubsequence) {
CHECK(m_currentSubsequenceClients.last() == &displayItem.client());
m_currentSubsequenceClients.removeLast();
}
}
#endif
if (isSkippingCache()) {
DCHECK(newItemSource == NewPainting);
displayItem.setSkippedCache();
}
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
if (newItemSource != FromCachedSubsequence)
m_currentChunkIsFromCachedSubsequence = false;
size_t lastChunkIndex = m_newPaintChunks.lastChunkIndex();
if (m_newPaintChunks.incrementDisplayItemIndex(displayItem)) {
DCHECK(lastChunkIndex != m_newPaintChunks.lastChunkIndex());
if (lastChunkIndex != kNotFound)
generateChunkRasterInvalidationRects(m_newPaintChunks.paintChunkAt(lastChunkIndex));
m_currentChunkIsFromCachedSubsequence = true;
}
}
#if DCHECK_IS_ON()
// Verify noop begin/end pairs have been removed.
if (m_newDisplayItemList.size() >= 2 && displayItem.isEnd()) {
const auto& beginDisplayItem = m_newDisplayItemList[m_newDisplayItemList.size() - 2];
if (beginDisplayItem.isBegin() && beginDisplayItem.getType() != DisplayItem::kSubsequence && !beginDisplayItem.drawsContent())
DCHECK(!displayItem.isEndAndPairedWith(beginDisplayItem.getType()));
}
size_t index = findMatchingItemFromIndex(displayItem.getId(), m_newDisplayItemIndicesByClient, m_newDisplayItemList);
if (index != kNotFound) {
#ifndef NDEBUG
showDebugData();
WTFLogAlways("DisplayItem %s has duplicated id with previous %s (index=%d)\n",
displayItem.asDebugString().utf8().data(), m_newDisplayItemList[index].asDebugString().utf8().data(), static_cast<int>(index));
#endif
NOTREACHED();
}
addItemToIndexIfNeeded(displayItem, m_newDisplayItemList.size() - 1, m_newDisplayItemIndicesByClient);
#endif // DCHECK_IS_ON()
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled())
checkUnderInvalidation();
}
void PaintController::updateCurrentPaintChunkProperties(const PaintChunk::Id* id, const PaintChunkProperties& newProperties)
{
m_newPaintChunks.updateCurrentPaintChunkProperties(id, newProperties);
}
const PaintChunkProperties& PaintController::currentPaintChunkProperties() const
{
return m_newPaintChunks.currentPaintChunkProperties();
}
void PaintController::invalidateAll()
{
// Can only be called during layout/paintInvalidation, not during painting.
DCHECK(m_newDisplayItemList.isEmpty());
m_currentPaintArtifact.reset();
m_currentCacheGeneration.invalidate();
}
bool PaintController::clientCacheIsValid(const DisplayItemClient& client) const
{
#if CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS
CHECK(client.isAlive());
#endif
if (isSkippingCache())
return false;
return client.displayItemsAreCached(m_currentCacheGeneration);
}
size_t PaintController::findMatchingItemFromIndex(const DisplayItem::Id& id, const IndicesByClientMap& displayItemIndicesByClient, const DisplayItemList& list)
{
IndicesByClientMap::const_iterator it = displayItemIndicesByClient.find(&id.client);
if (it == displayItemIndicesByClient.end())
return kNotFound;
const Vector<size_t>& indices = it->value;
for (size_t index : indices) {
const DisplayItem& existingItem = list[index];
DCHECK(!existingItem.hasValidClient() || existingItem.client() == id.client);
if (id == existingItem.getId())
return index;
}
return kNotFound;
}
void PaintController::addItemToIndexIfNeeded(const DisplayItem& displayItem, size_t index, IndicesByClientMap& displayItemIndicesByClient)
{
if (!displayItem.isCacheable())
return;
IndicesByClientMap::iterator it = displayItemIndicesByClient.find(&displayItem.client());
Vector<size_t>& indices = it == displayItemIndicesByClient.end() ?
displayItemIndicesByClient.add(&displayItem.client(), Vector<size_t>()).storedValue->value : it->value;
indices.append(index);
}
size_t PaintController::findCachedItem(const DisplayItem::Id& id)
{
DCHECK(clientCacheIsValid(id.client));
// Try to find the item sequentially first. This is fast if the current list and the new list are in
// the same order around the new item. If found, we don't need to update and lookup the index.
for (size_t i = m_nextItemToMatch; i < m_currentPaintArtifact.getDisplayItemList().size(); ++i) {
// We encounter an item that has already been copied which indicates we can't do sequential matching.
const DisplayItem& item = m_currentPaintArtifact.getDisplayItemList()[i];
if (!item.hasValidClient())
break;
if (id == item.getId()) {
#ifndef NDEBUG
++m_numSequentialMatches;
#endif
return i;
}
// We encounter a different cacheable item which also indicates we can't do sequential matching.
if (item.isCacheable())
break;
}
size_t foundIndex = findMatchingItemFromIndex(id, m_outOfOrderItemIndices, m_currentPaintArtifact.getDisplayItemList());
if (foundIndex != kNotFound) {
#ifndef NDEBUG
++m_numOutOfOrderMatches;
#endif
return foundIndex;
}
return findOutOfOrderCachedItemForward(id);
}
// Find forward for the item and index all skipped indexable items.
size_t PaintController::findOutOfOrderCachedItemForward(const DisplayItem::Id& id)
{
for (size_t i = m_nextItemToIndex; i < m_currentPaintArtifact.getDisplayItemList().size(); ++i) {
const DisplayItem& item = m_currentPaintArtifact.getDisplayItemList()[i];
DCHECK(item.hasValidClient());
if (id == item.getId()) {
#ifndef NDEBUG
++m_numSequentialMatches;
#endif
return i;
}
if (item.isCacheable()) {
#ifndef NDEBUG
++m_numIndexedItems;
#endif
addItemToIndexIfNeeded(item, i, m_outOfOrderItemIndices);
}
}
#ifndef NDEBUG
showDebugData();
LOG(ERROR) << id.client.debugName() << ":" << DisplayItem::typeAsDebugString(id.type);
#endif
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled())
CHECK(false) << "Can't find cached display item";
// We did not find the cached display item. This should be impossible, but may occur if there is a bug
// in the system, such as under-invalidation, incorrect cache checking or duplicate display ids.
// In this case, the caller should fall back to repaint the display item.
return kNotFound;
}
// Copies a cached subsequence from current list to the new list. On return,
// |cachedItemIndex| points to the item after the EndSubsequence item of the subsequence.
// When paintUnderInvaldiationCheckingEnabled() we'll not actually copy the subsequence,
// but mark the begin and end of the subsequence for under-invalidation checking.
void PaintController::copyCachedSubsequence(size_t& cachedItemIndex)
{
DisplayItem* cachedItem = &m_currentPaintArtifact.getDisplayItemList()[cachedItemIndex];
DCHECK(cachedItem->getType() == DisplayItem::kSubsequence);
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled()) {
DCHECK(!isCheckingUnderInvalidation());
m_underInvalidationCheckingBegin = cachedItemIndex;
m_underInvalidationMessagePrefix = "(In cached subsequence of " + cachedItem->client().debugName() + ")";
}
DisplayItem::Id endSubsequenceId(cachedItem->client(), DisplayItem::kEndSubsequence);
Vector<PaintChunk>::const_iterator cachedChunk;
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
cachedChunk = m_currentPaintArtifact.findChunkByDisplayItemIndex(cachedItemIndex);
updateCurrentPaintChunkProperties(cachedChunk->id ? &*cachedChunk->id : nullptr, cachedChunk->properties);
} else {
// This is to avoid compilation error about uninitialized variable on Windows.
cachedChunk = m_currentPaintArtifact.paintChunks().begin();
}
while (true) {
DCHECK(cachedItem->hasValidClient());
#if CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS
CHECK(cachedItem->client().isAlive());
#endif
++m_numCachedNewItems;
bool metEndSubsequence = cachedItem->getId() == endSubsequenceId;
if (!RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled()) {
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && cachedItemIndex == cachedChunk->endIndex) {
++cachedChunk;
updateCurrentPaintChunkProperties(cachedChunk->id ? &*cachedChunk->id : nullptr, cachedChunk->properties);
}
processNewItem(m_newDisplayItemList.appendByMoving(*cachedItem), FromCachedSubsequence);
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
DCHECK((!m_newPaintChunks.lastChunk().id && !cachedChunk->id) || m_newPaintChunks.lastChunk().matches(*cachedChunk));
}
++cachedItemIndex;
if (metEndSubsequence)
break;
// We should always be able to find the EndSubsequence display item.
DCHECK(cachedItemIndex < m_currentPaintArtifact.getDisplayItemList().size());
cachedItem = &m_currentPaintArtifact.getDisplayItemList()[cachedItemIndex];
}
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled()) {
m_underInvalidationCheckingEnd = cachedItemIndex;
DCHECK(isCheckingUnderInvalidation());
}
}
static IntRect visualRectForDisplayItem(const DisplayItem& displayItem, const LayoutSize& offsetFromLayoutObject)
{
LayoutRect visualRect = displayItem.client().visualRect();
visualRect.move(-offsetFromLayoutObject);
return enclosingIntRect(visualRect);
}
void PaintController::resetCurrentListIndices()
{
m_nextItemToMatch = 0;
m_nextItemToIndex = 0;
m_nextChunkToMatch = 0;
m_underInvalidationCheckingBegin = 0;
m_underInvalidationCheckingEnd = 0;
m_skippedProbableUnderInvalidationCount = 0;
}
void PaintController::commitNewDisplayItems(const LayoutSize& offsetFromLayoutObject)
{
TRACE_EVENT2("blink,benchmark", "PaintController::commitNewDisplayItems",
"current_display_list_size", (int)m_currentPaintArtifact.getDisplayItemList().size(),
"num_non_cached_new_items", (int)m_newDisplayItemList.size() - m_numCachedNewItems);
m_numCachedNewItems = 0;
// These data structures are used during painting only.
DCHECK(!isSkippingCache());
#if DCHECK_IS_ON()
m_newDisplayItemIndicesByClient.clear();
#endif
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && !m_newDisplayItemList.isEmpty())
generateChunkRasterInvalidationRects(m_newPaintChunks.lastChunk());
SkPictureGpuAnalyzer gpuAnalyzer;
m_currentCacheGeneration = DisplayItemClient::CacheGenerationOrInvalidationReason::next();
Vector<const DisplayItemClient*> skippedCacheClients;
for (const auto& item : m_newDisplayItemList) {
// No reason to continue the analysis once we have a veto.
if (gpuAnalyzer.suitableForGpuRasterization())
item.analyzeForGpuRasterization(gpuAnalyzer);
// TODO(wkorman): Only compute and append visual rect for drawings.
m_newDisplayItemList.appendVisualRect(visualRectForDisplayItem(item, offsetFromLayoutObject));
if (item.isCacheable()) {
item.client().setDisplayItemsCached(m_currentCacheGeneration);
} else {
if (item.client().isJustCreated())
item.client().clearIsJustCreated();
if (item.skippedCache())
skippedCacheClients.append(&item.client());
}
}
for (auto* client : skippedCacheClients)
client->setDisplayItemsUncached();
// The new list will not be appended to again so we can release unused memory.
m_newDisplayItemList.shrinkToFit();
m_currentPaintArtifact = PaintArtifact(std::move(m_newDisplayItemList), m_newPaintChunks.releasePaintChunks(), gpuAnalyzer.suitableForGpuRasterization());
resetCurrentListIndices();
m_outOfOrderItemIndices.clear();
m_outOfOrderChunkIndices.clear();
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
for (const auto& chunk : m_currentPaintArtifact.paintChunks()) {
if (chunk.id && chunk.id->client.isJustCreated())
chunk.id->client.clearIsJustCreated();
}
}
// We'll allocate the initial buffer when we start the next paint.
m_newDisplayItemList = DisplayItemList(0);
#if CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS
CHECK(m_currentSubsequenceClients.isEmpty());
DisplayItemClient::endShouldKeepAliveAllClients(this);
#endif
#ifndef NDEBUG
m_numSequentialMatches = 0;
m_numOutOfOrderMatches = 0;
m_numIndexedItems = 0;
#endif
}
size_t PaintController::approximateUnsharedMemoryUsage() const
{
size_t memoryUsage = sizeof(*this);
// Memory outside this class due to m_currentPaintArtifact.
memoryUsage += m_currentPaintArtifact.approximateUnsharedMemoryUsage() - sizeof(m_currentPaintArtifact);
// TODO(jbroman): If display items begin to have significant external memory
// usage that's not shared with the embedder, we should account for it here.
//
// External objects, shared with the embedder, such as SkPicture, should be
// excluded to avoid double counting. It is the embedder's responsibility to
// count such objects.
//
// At time of writing, the only known case of unshared external memory was
// the rounded clips vector in ClipDisplayItem, which is not expected to
// contribute significantly to memory usage.
// Memory outside this class due to m_newDisplayItemList.
DCHECK(m_newDisplayItemList.isEmpty());
memoryUsage += m_newDisplayItemList.memoryUsageInBytes();
return memoryUsage;
}
void PaintController::appendDebugDrawingAfterCommit(const DisplayItemClient& displayItemClient, sk_sp<SkPicture> picture, const LayoutSize& offsetFromLayoutObject)
{
DCHECK(m_newDisplayItemList.isEmpty());
DrawingDisplayItem& displayItem = m_currentPaintArtifact.getDisplayItemList().allocateAndConstruct<DrawingDisplayItem>(displayItemClient, DisplayItem::kDebugDrawing, std::move(picture));
displayItem.setSkippedCache();
// TODO(wkorman): Only compute and append visual rect for drawings.
m_currentPaintArtifact.getDisplayItemList().appendVisualRect(visualRectForDisplayItem(displayItem, offsetFromLayoutObject));
}
void PaintController::generateChunkRasterInvalidationRects(PaintChunk& newChunk)
{
DCHECK(RuntimeEnabledFeatures::slimmingPaintV2Enabled());
if (m_currentChunkIsFromCachedSubsequence)
return;
if (!newChunk.id) {
newChunk.rasterInvalidationRects.append(FloatRect(LayoutRect::infiniteIntRect()));
return;
}
// Try to match old chunk sequentially first.
const auto& oldChunks = m_currentPaintArtifact.paintChunks();
while (m_nextChunkToMatch < oldChunks.size()) {
const PaintChunk& oldChunk = oldChunks[m_nextChunkToMatch];
if (newChunk.matches(oldChunk)) {
generateChunkRasterInvalidationRectsComparingOldChunk(newChunk, oldChunk);
++m_nextChunkToMatch;
return;
}
// Add skipped old chunks into the index.
if (oldChunk.id) {
auto it = m_outOfOrderChunkIndices.find(&oldChunk.id->client);
Vector<size_t>& indices = it == m_outOfOrderChunkIndices.end() ?
m_outOfOrderChunkIndices.add(&oldChunk.id->client, Vector<size_t>()).storedValue->value : it->value;
indices.append(m_nextChunkToMatch);
}
++m_nextChunkToMatch;
}
// Sequential matching reaches the end. Find from the out-of-order index.
auto it = m_outOfOrderChunkIndices.find(&newChunk.id->client);
if (it != m_outOfOrderChunkIndices.end()) {
for (size_t i : it->value) {
if (newChunk.matches(oldChunks[i])) {
generateChunkRasterInvalidationRectsComparingOldChunk(newChunk, oldChunks[i]);
return;
}
}
}
// We reach here because the chunk is new.
newChunk.rasterInvalidationRects.append(FloatRect(LayoutRect::infiniteIntRect()));
}
void PaintController::generateChunkRasterInvalidationRectsComparingOldChunk(PaintChunk& newChunk, const PaintChunk& oldChunk)
{
DCHECK(RuntimeEnabledFeatures::slimmingPaintV2Enabled());
// TODO(wangxianzhu): Support raster invalidation for reordered display items without invalidating
// display item clients. Currently we invalidate display item clients ensuring raster invalidation.
// TODO(wangxianzhu): Handle PaintInvalidationIncremental.
// TODO(wangxianzhu): Optimize paint offset change.
// Maps from each client to the index of the first drawing-content display item of the client.
HashMap<const DisplayItemClient*, size_t> oldChunkClients;
for (size_t i = oldChunk.beginIndex; i < oldChunk.endIndex; ++i) {
const DisplayItem& oldItem = m_currentPaintArtifact.getDisplayItemList()[i];
// oldItem.hasValidClient() indicates that the item has not been copied as a cached item into
// m_newDislayItemList, so the item either disappeared or changed, and needs raster invalidation.
if (oldItem.hasValidClient() && oldItem.drawsContent() && oldChunkClients.add(&oldItem.client(), i).isNewEntry)
newChunk.rasterInvalidationRects.append(m_currentPaintArtifact.getDisplayItemList().visualRect(i));
}
HashSet<const DisplayItemClient*> newChunkClients;
for (size_t i = newChunk.beginIndex; i < newChunk.endIndex; ++i) {
const DisplayItem& newItem = m_newDisplayItemList[i];
if (newItem.drawsContent()) {
if (!clientCacheIsValid(newItem.client())) {
if (newChunkClients.add(&newItem.client()).isNewEntry)
newChunk.rasterInvalidationRects.append(newItem.client().visualRect());
} else {
// The cached item was moved from the old chunk which should not contain any item of the client now.
DCHECK(!oldChunkClients.contains(&newItem.client()));
}
}
}
}
void PaintController::showUnderInvalidationError(const char* reason, const DisplayItem& newItem, const DisplayItem* oldItem) const
{
LOG(ERROR) << m_underInvalidationMessagePrefix << " " << reason;
#ifndef NDEBUG
LOG(ERROR) << "New display item: " << newItem.asDebugString();
LOG(ERROR) << "Old display item: " << (oldItem ? oldItem->asDebugString() : "None");
#else
LOG(ERROR) << "Run debug build to get more details.";
#endif
LOG(ERROR) << "See http://crbug.com/619103.";
#ifndef NDEBUG
const SkPicture* newPicture = newItem.isDrawing() ? static_cast<const DrawingDisplayItem&>(newItem).picture() : nullptr;
const SkPicture* oldPicture = oldItem && oldItem->isDrawing() ? static_cast<const DrawingDisplayItem*>(oldItem)->picture() : nullptr;
LOG(INFO) << "new picture:\n" << (newPicture ? pictureAsDebugString(newPicture) : "None");
LOG(INFO) << "old picture:\n" << (oldPicture ? pictureAsDebugString(oldPicture) : "None");
showDebugData();
#endif // NDEBUG
}
void PaintController::checkUnderInvalidation()
{
DCHECK(RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled());
if (!isCheckingUnderInvalidation())
return;
const DisplayItem& newItem = m_newDisplayItemList.last();
size_t oldItemIndex = m_underInvalidationCheckingBegin + m_skippedProbableUnderInvalidationCount;
DisplayItem* oldItem = oldItemIndex < m_currentPaintArtifact.getDisplayItemList().size() ? &m_currentPaintArtifact.getDisplayItemList()[oldItemIndex] : nullptr;
bool oldAndNewEqual = oldItem && newItem.equals(*oldItem);
if (!oldAndNewEqual) {
if (newItem.isBegin()) {
// Temporarily skip mismatching begin display item which may be removed when we remove a no-op pair.
++m_skippedProbableUnderInvalidationCount;
return;
}
if (newItem.isDrawing() && m_skippedProbableUnderInvalidationCount == 1) {
DCHECK_GE(m_newDisplayItemList.size(), 2u);
if (m_newDisplayItemList[m_newDisplayItemList.size() - 2].getType() == DisplayItem::kBeginCompositing) {
// This might be a drawing item between a pair of begin/end compositing display items that will be folded
// into a single drawing display item.
++m_skippedProbableUnderInvalidationCount;
return;
}
}
}
if (m_skippedProbableUnderInvalidationCount || !oldAndNewEqual) {
// If we ever skipped reporting any under-invalidations, report the earliest one.
showUnderInvalidationError("under-invalidation: display item changed",
m_newDisplayItemList[m_newDisplayItemList.size() - m_skippedProbableUnderInvalidationCount - 1],
&m_currentPaintArtifact.getDisplayItemList()[m_underInvalidationCheckingBegin]);
CHECK(false);
}
// Discard the forced repainted display item and move the cached item into m_newDisplayItemList.
// This is to align with the non-under-invalidation-checking path to empty the original cached slot,
// leaving only disappeared or invalidated display items in the old list after painting.
m_newDisplayItemList.removeLast();
m_newDisplayItemList.appendByMoving(*oldItem);
++m_underInvalidationCheckingBegin;
}
String PaintController::displayItemListAsDebugString(const DisplayItemList& list) const
{
StringBuilder stringBuilder;
size_t i = 0;
for (auto it = list.begin(); it != list.end(); ++it, ++i) {
const DisplayItem& displayItem = *it;
if (i)
stringBuilder.append(",\n");
stringBuilder.append(String::format("{index: %d, ", (int)i));
#ifndef NDEBUG
displayItem.dumpPropertiesAsDebugString(stringBuilder);
#else
stringBuilder.append(String::format("clientDebugName: %s", displayItem.client().debugName().ascii().data()));
#endif
if (displayItem.hasValidClient()) {
do {
#if CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS
if (!displayItem.client().isAlive()) {
stringBuilder.append(", clientIsAlive: false");
break;
}
#endif
stringBuilder.append(", cacheIsValid: ");
stringBuilder.append(clientCacheIsValid(displayItem.client()) ? "true" : "false");
} while (false);
}
if (list.hasVisualRect(i)) {
IntRect visualRect = list.visualRect(i);
stringBuilder.append(String::format(", visualRect: [%d,%d %dx%d]",
visualRect.x(), visualRect.y(),
visualRect.width(), visualRect.height()));
}
stringBuilder.append('}');
}
return stringBuilder.toString();
}
void PaintController::showDebugData() const
{
WTFLogAlways("current display item list: [%s]\n", displayItemListAsDebugString(m_currentPaintArtifact.getDisplayItemList()).utf8().data());
WTFLogAlways("new display item list: [%s]\n", displayItemListAsDebugString(m_newDisplayItemList).utf8().data());
}
} // namespace blink