| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "config.h" | |
| 6 #include "platform/graphics/paint/DisplayItemList.h" | |
| 7 | |
| 8 #include "platform/NotImplemented.h" | |
| 9 #include "platform/TraceEvent.h" | |
| 10 #include "platform/graphics/GraphicsLayer.h" | |
| 11 #include "platform/graphics/paint/DrawingDisplayItem.h" | |
| 12 | |
| 13 #ifndef NDEBUG | |
| 14 #include "platform/graphics/LoggingCanvas.h" | |
| 15 #include "wtf/text/StringBuilder.h" | |
| 16 #include <stdio.h> | |
| 17 #endif | |
| 18 | |
| 19 namespace blink { | |
| 20 | |
| 21 const PaintArtifact& DisplayItemList::paintArtifact() const | |
| 22 { | |
| 23 ASSERT(m_newDisplayItems.isEmpty()); | |
| 24 ASSERT(m_newPaintChunks.isInInitialState()); | |
| 25 return m_currentPaintArtifact; | |
| 26 } | |
| 27 | |
| 28 bool DisplayItemList::lastDisplayItemIsNoopBegin() const | |
| 29 { | |
| 30 if (m_newDisplayItems.isEmpty()) | |
| 31 return false; | |
| 32 | |
| 33 const auto& lastDisplayItem = m_newDisplayItems.last(); | |
| 34 return lastDisplayItem.isBegin() && !lastDisplayItem.drawsContent(); | |
| 35 } | |
| 36 | |
| 37 void DisplayItemList::removeLastDisplayItem() | |
| 38 { | |
| 39 if (m_newDisplayItems.isEmpty()) | |
| 40 return; | |
| 41 | |
| 42 #if ENABLE(ASSERT) | |
| 43 // Also remove the index pointing to the removed display item. | |
| 44 DisplayItemIndicesByClientMap::iterator it = m_newDisplayItemIndicesByClient
.find(m_newDisplayItems.last().client()); | |
| 45 if (it != m_newDisplayItemIndicesByClient.end()) { | |
| 46 Vector<size_t>& indices = it->value; | |
| 47 if (!indices.isEmpty() && indices.last() == (m_newDisplayItems.size() -
1)) | |
| 48 indices.removeLast(); | |
| 49 } | |
| 50 #endif | |
| 51 m_newDisplayItems.removeLast(); | |
| 52 | |
| 53 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) | |
| 54 m_newPaintChunks.decrementDisplayItemIndex(); | |
| 55 } | |
| 56 | |
| 57 void DisplayItemList::processNewItem(DisplayItem& displayItem) | |
| 58 { | |
| 59 ASSERT(!m_constructionDisabled); | |
| 60 ASSERT(!skippingCache() || !displayItem.isCached()); | |
| 61 | |
| 62 if (displayItem.isCached()) | |
| 63 ++m_numCachedItems; | |
| 64 | |
| 65 #if ENABLE(ASSERT) | |
| 66 // Verify noop begin/end pairs have been removed. | |
| 67 if (m_newDisplayItems.size() >= 2 && displayItem.isEnd()) { | |
| 68 const auto& beginDisplayItem = m_newDisplayItems[m_newDisplayItems.size(
) - 2]; | |
| 69 if (beginDisplayItem.isBegin() && !beginDisplayItem.isSubsequence() && !
beginDisplayItem.drawsContent()) | |
| 70 ASSERT(!displayItem.isEndAndPairedWith(beginDisplayItem.type())); | |
| 71 } | |
| 72 #endif | |
| 73 | |
| 74 if (!m_scopeStack.isEmpty()) | |
| 75 displayItem.setScope(m_scopeStack.last()); | |
| 76 | |
| 77 #if ENABLE(ASSERT) | |
| 78 size_t index = findMatchingItemFromIndex(displayItem.nonCachedId(), m_newDis
playItemIndicesByClient, m_newDisplayItems); | |
| 79 if (index != kNotFound) { | |
| 80 #ifndef NDEBUG | |
| 81 showDebugData(); | |
| 82 WTFLogAlways("DisplayItem %s has duplicated id with previous %s (index=%
d)\n", | |
| 83 displayItem.asDebugString().utf8().data(), m_newDisplayItems[index].
asDebugString().utf8().data(), static_cast<int>(index)); | |
| 84 #endif | |
| 85 ASSERT_NOT_REACHED(); | |
| 86 } | |
| 87 addItemToIndexIfNeeded(displayItem, m_newDisplayItems.size() - 1, m_newDispl
ayItemIndicesByClient); | |
| 88 #endif // ENABLE(ASSERT) | |
| 89 | |
| 90 if (skippingCache()) | |
| 91 displayItem.setSkippedCache(); | |
| 92 | |
| 93 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) | |
| 94 m_newPaintChunks.incrementDisplayItemIndex(); | |
| 95 } | |
| 96 | |
| 97 void DisplayItemList::updateCurrentPaintChunkProperties(const PaintChunkProperti
es& newProperties) | |
| 98 { | |
| 99 m_newPaintChunks.updateCurrentPaintChunkProperties(newProperties); | |
| 100 } | |
| 101 | |
| 102 void DisplayItemList::beginScope() | |
| 103 { | |
| 104 ASSERT_WITH_SECURITY_IMPLICATION(m_nextScope < UINT_MAX); | |
| 105 m_scopeStack.append(m_nextScope++); | |
| 106 beginSkippingCache(); | |
| 107 } | |
| 108 | |
| 109 void DisplayItemList::endScope() | |
| 110 { | |
| 111 m_scopeStack.removeLast(); | |
| 112 endSkippingCache(); | |
| 113 } | |
| 114 | |
| 115 void DisplayItemList::invalidate(const DisplayItemClientWrapper& client, PaintIn
validationReason paintInvalidationReason, const IntRect& previousPaintInvalidati
onRect, const IntRect& newPaintInvalidationRect) | |
| 116 { | |
| 117 invalidateClient(client); | |
| 118 | |
| 119 if (RuntimeEnabledFeatures::slimmingPaintSynchronizedPaintingEnabled()) { | |
| 120 Invalidation invalidation = { previousPaintInvalidationRect, paintInvali
dationReason }; | |
| 121 if (!previousPaintInvalidationRect.isEmpty()) | |
| 122 m_invalidations.append(invalidation); | |
| 123 if (newPaintInvalidationRect != previousPaintInvalidationRect && !newPai
ntInvalidationRect.isEmpty()) { | |
| 124 invalidation.rect = newPaintInvalidationRect; | |
| 125 m_invalidations.append(invalidation); | |
| 126 } | |
| 127 } | |
| 128 } | |
| 129 | |
| 130 void DisplayItemList::invalidateClient(const DisplayItemClientWrapper& client) | |
| 131 { | |
| 132 invalidateUntracked(client.displayItemClient()); | |
| 133 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && m_trackedPaintInvali
dationObjects) | |
| 134 m_trackedPaintInvalidationObjects->append(client.debugName()); | |
| 135 } | |
| 136 | |
| 137 void DisplayItemList::invalidateUntracked(DisplayItemClient client) | |
| 138 { | |
| 139 // This can be called during painting, but we can't invalidate already paint
ed clients. | |
| 140 ASSERT(!m_newDisplayItemIndicesByClient.contains(client)); | |
| 141 updateValidlyCachedClientsIfNeeded(); | |
| 142 m_validlyCachedClients.remove(client); | |
| 143 } | |
| 144 | |
| 145 void DisplayItemList::invalidateAll() | |
| 146 { | |
| 147 // Can only be called during layout/paintInvalidation, not during painting. | |
| 148 ASSERT(m_newDisplayItems.isEmpty()); | |
| 149 m_currentPaintArtifact.reset(); | |
| 150 m_validlyCachedClients.clear(); | |
| 151 m_validlyCachedClientsDirty = false; | |
| 152 | |
| 153 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && m_trackedPaintInvali
dationObjects) | |
| 154 m_trackedPaintInvalidationObjects->append("##ALL##"); | |
| 155 } | |
| 156 | |
| 157 bool DisplayItemList::clientCacheIsValid(DisplayItemClient client) const | |
| 158 { | |
| 159 if (skippingCache()) | |
| 160 return false; | |
| 161 updateValidlyCachedClientsIfNeeded(); | |
| 162 return m_validlyCachedClients.contains(client); | |
| 163 } | |
| 164 | |
| 165 void DisplayItemList::invalidatePaintOffset(const DisplayItemClientWrapper& clie
nt) | |
| 166 { | |
| 167 ASSERT(RuntimeEnabledFeatures::slimmingPaintOffsetCachingEnabled()); | |
| 168 invalidateClient(client); | |
| 169 | |
| 170 #if ENABLE(ASSERT) | |
| 171 ASSERT(!paintOffsetWasInvalidated(client.displayItemClient())); | |
| 172 m_clientsWithPaintOffsetInvalidations.add(client.displayItemClient()); | |
| 173 #endif | |
| 174 } | |
| 175 | |
| 176 #if ENABLE(ASSERT) | |
| 177 bool DisplayItemList::paintOffsetWasInvalidated(DisplayItemClient client) const | |
| 178 { | |
| 179 ASSERT(RuntimeEnabledFeatures::slimmingPaintOffsetCachingEnabled()); | |
| 180 return m_clientsWithPaintOffsetInvalidations.contains(client); | |
| 181 } | |
| 182 #endif | |
| 183 | |
| 184 size_t DisplayItemList::findMatchingItemFromIndex(const DisplayItem::Id& id, con
st DisplayItemIndicesByClientMap& displayItemIndicesByClient, const DisplayItems
& list) | |
| 185 { | |
| 186 DisplayItemIndicesByClientMap::const_iterator it = displayItemIndicesByClien
t.find(id.client); | |
| 187 if (it == displayItemIndicesByClient.end()) | |
| 188 return kNotFound; | |
| 189 | |
| 190 const Vector<size_t>& indices = it->value; | |
| 191 for (size_t index : indices) { | |
| 192 const DisplayItem& existingItem = list[index]; | |
| 193 ASSERT(!existingItem.isValid() || existingItem.client() == id.client); | |
| 194 if (existingItem.isValid() && id.matches(existingItem)) | |
| 195 return index; | |
| 196 } | |
| 197 | |
| 198 return kNotFound; | |
| 199 } | |
| 200 | |
| 201 void DisplayItemList::addItemToIndexIfNeeded(const DisplayItem& displayItem, siz
e_t index, DisplayItemIndicesByClientMap& displayItemIndicesByClient) | |
| 202 { | |
| 203 if (!displayItem.isCacheable()) | |
| 204 return; | |
| 205 | |
| 206 DisplayItemIndicesByClientMap::iterator it = displayItemIndicesByClient.find
(displayItem.client()); | |
| 207 Vector<size_t>& indices = it == displayItemIndicesByClient.end() ? | |
| 208 displayItemIndicesByClient.add(displayItem.client(), Vector<size_t>()).s
toredValue->value : it->value; | |
| 209 indices.append(index); | |
| 210 } | |
| 211 | |
| 212 struct DisplayItemList::OutOfOrderIndexContext { | |
| 213 OutOfOrderIndexContext(DisplayItems::iterator begin) : nextItemToIndex(begin
) { } | |
| 214 | |
| 215 DisplayItems::iterator nextItemToIndex; | |
| 216 DisplayItemIndicesByClientMap displayItemIndicesByClient; | |
| 217 }; | |
| 218 | |
| 219 DisplayItems::iterator DisplayItemList::findOutOfOrderCachedItem(const DisplayIt
em::Id& id, OutOfOrderIndexContext& context) | |
| 220 { | |
| 221 ASSERT(clientCacheIsValid(id.client)); | |
| 222 | |
| 223 size_t foundIndex = findMatchingItemFromIndex(id, context.displayItemIndices
ByClient, m_currentPaintArtifact.displayItems()); | |
| 224 if (foundIndex != kNotFound) | |
| 225 return m_currentPaintArtifact.displayItems().begin() + foundIndex; | |
| 226 | |
| 227 return findOutOfOrderCachedItemForward(id, context); | |
| 228 } | |
| 229 | |
| 230 // Find forward for the item and index all skipped indexable items. | |
| 231 DisplayItems::iterator DisplayItemList::findOutOfOrderCachedItemForward(const Di
splayItem::Id& id, OutOfOrderIndexContext& context) | |
| 232 { | |
| 233 DisplayItems::iterator currentEnd = m_currentPaintArtifact.displayItems().en
d(); | |
| 234 for (; context.nextItemToIndex != currentEnd; ++context.nextItemToIndex) { | |
| 235 const DisplayItem& item = *context.nextItemToIndex; | |
| 236 ASSERT(item.isValid()); | |
| 237 if (item.isCacheable() && clientCacheIsValid(item.client())) { | |
| 238 if (id.matches(item)) | |
| 239 return context.nextItemToIndex++; | |
| 240 | |
| 241 addItemToIndexIfNeeded(item, context.nextItemToIndex - m_currentPain
tArtifact.displayItems().begin(), context.displayItemIndicesByClient); | |
| 242 } | |
| 243 } | |
| 244 return currentEnd; | |
| 245 } | |
| 246 | |
| 247 void DisplayItemList::copyCachedSubsequence(DisplayItems::iterator& currentIt, D
isplayItems& updatedList) | |
| 248 { | |
| 249 ASSERT(currentIt->isSubsequence()); | |
| 250 ASSERT(!currentIt->scope()); | |
| 251 DisplayItem::Id endSubsequenceId(currentIt->client(), DisplayItem::subsequen
ceTypeToEndSubsequenceType(currentIt->type()), 0); | |
| 252 do { | |
| 253 // We should always find the EndSubsequence display item. | |
| 254 ASSERT(currentIt != m_currentPaintArtifact.displayItems().end()); | |
| 255 ASSERT(currentIt->isValid()); | |
| 256 updatedList.appendByMoving(*currentIt); | |
| 257 ++currentIt; | |
| 258 } while (!endSubsequenceId.matches(updatedList.last())); | |
| 259 } | |
| 260 | |
| 261 // Update the existing display items by removing invalidated entries, updating | |
| 262 // repainted ones, and appending new items. | |
| 263 // - For cached drawing display item, copy the corresponding cached DrawingDispl
ayItem; | |
| 264 // - For cached subsequence display item, copy the cached display items between
the | |
| 265 // corresponding SubsequenceDisplayItem and EndSubsequenceDisplayItem (incl.); | |
| 266 // - Otherwise, copy the new display item. | |
| 267 // | |
| 268 // The algorithm is O(|m_currentDisplayItems| + |m_newDisplayItems|). | |
| 269 // Coefficients are related to the ratio of out-of-order CachedDisplayItems | |
| 270 // and the average number of (Drawing|Subsequence)DisplayItems per client. | |
| 271 // | |
| 272 void DisplayItemList::commitNewDisplayItems(GraphicsLayer* graphicsLayer) | |
| 273 { | |
| 274 TRACE_EVENT2("blink,benchmark", "DisplayItemList::commitNewDisplayItems", | |
| 275 "current_display_list_size", (int)m_currentPaintArtifact.displayItems().
size(), | |
| 276 "num_non_cached_new_items", (int)m_newDisplayItems.size() - m_numCachedI
tems); | |
| 277 | |
| 278 if (RuntimeEnabledFeatures::slimmingPaintSynchronizedPaintingEnabled()) { | |
| 279 for (const auto& invalidation : m_invalidations) | |
| 280 graphicsLayer->setNeedsDisplayInRect(invalidation.rect, invalidation
.invalidationReason); | |
| 281 m_invalidations.clear(); | |
| 282 m_clientsCheckedPaintInvalidation.clear(); | |
| 283 } | |
| 284 | |
| 285 // These data structures are used during painting only. | |
| 286 ASSERT(m_scopeStack.isEmpty()); | |
| 287 m_scopeStack.clear(); | |
| 288 m_nextScope = 1; | |
| 289 ASSERT(!skippingCache()); | |
| 290 #if ENABLE(ASSERT) | |
| 291 m_newDisplayItemIndicesByClient.clear(); | |
| 292 m_clientsWithPaintOffsetInvalidations.clear(); | |
| 293 #endif | |
| 294 | |
| 295 if (m_currentPaintArtifact.isEmpty()) { | |
| 296 #if ENABLE(ASSERT) | |
| 297 for (const auto& item : m_newDisplayItems) | |
| 298 ASSERT(!item.isCached()); | |
| 299 #endif | |
| 300 m_currentPaintArtifact.displayItems().swap(m_newDisplayItems); | |
| 301 m_currentPaintArtifact.paintChunks() = m_newPaintChunks.releasePaintChun
ks(); | |
| 302 m_validlyCachedClientsDirty = true; | |
| 303 m_numCachedItems = 0; | |
| 304 return; | |
| 305 } | |
| 306 | |
| 307 updateValidlyCachedClientsIfNeeded(); | |
| 308 | |
| 309 // Stores indices to valid DrawingDisplayItems in m_currentDisplayItems that
have not been matched | |
| 310 // by CachedDisplayItems during synchronized matching. The indexed items wil
l be matched | |
| 311 // by later out-of-order CachedDisplayItems in m_newDisplayItems. This ensur
es that when | |
| 312 // out-of-order CachedDisplayItems occur, we only traverse at most once over
m_currentDisplayItems | |
| 313 // looking for potential matches. Thus we can ensure that the algorithm runs
in linear time. | |
| 314 OutOfOrderIndexContext outOfOrderIndexContext(m_currentPaintArtifact.display
Items().begin()); | |
| 315 | |
| 316 // TODO(jbroman): Consider revisiting this heuristic. | |
| 317 DisplayItems updatedList(std::max(m_currentPaintArtifact.displayItems().used
CapacityInBytes(), m_newDisplayItems.usedCapacityInBytes())); | |
| 318 Vector<PaintChunk> updatedPaintChunks; | |
| 319 DisplayItems::iterator currentIt = m_currentPaintArtifact.displayItems().beg
in(); | |
| 320 DisplayItems::iterator currentEnd = m_currentPaintArtifact.displayItems().en
d(); | |
| 321 for (DisplayItems::iterator newIt = m_newDisplayItems.begin(); newIt != m_ne
wDisplayItems.end(); ++newIt) { | |
| 322 const DisplayItem& newDisplayItem = *newIt; | |
| 323 const DisplayItem::Id newDisplayItemId = newDisplayItem.nonCachedId(); | |
| 324 bool newDisplayItemHasCachedType = newDisplayItem.type() != newDisplayIt
emId.type; | |
| 325 | |
| 326 bool isSynchronized = currentIt != currentEnd && newDisplayItemId.matche
s(*currentIt); | |
| 327 | |
| 328 if (newDisplayItemHasCachedType) { | |
| 329 ASSERT(newDisplayItem.isCached()); | |
| 330 ASSERT(clientCacheIsValid(newDisplayItem.client()) || (RuntimeEnable
dFeatures::slimmingPaintOffsetCachingEnabled() && !paintOffsetWasInvalidated(new
DisplayItem.client()))); | |
| 331 if (!isSynchronized) { | |
| 332 currentIt = findOutOfOrderCachedItem(newDisplayItemId, outOfOrde
rIndexContext); | |
| 333 | |
| 334 if (currentIt == currentEnd) { | |
| 335 #ifndef NDEBUG | |
| 336 showDebugData(); | |
| 337 WTFLogAlways("%s not found in m_currentDisplayItems\n", newD
isplayItem.asDebugString().utf8().data()); | |
| 338 #endif | |
| 339 ASSERT_NOT_REACHED(); | |
| 340 // We did not find the cached display item. This should be i
mpossible, but may occur if there is a bug | |
| 341 // in the system, such as under-invalidation, incorrect cach
e checking or duplicate display ids. | |
| 342 // In this case, attempt to recover rather than crashing or
bailing on display of the rest of the display list. | |
| 343 continue; | |
| 344 } | |
| 345 } | |
| 346 #if ENABLE(ASSERT) | |
| 347 if (RuntimeEnabledFeatures::slimmingPaintUnderInvalidationCheckingEn
abled()) { | |
| 348 DisplayItems::iterator temp = currentIt; | |
| 349 checkUnderInvalidation(newIt, temp); | |
| 350 } | |
| 351 #endif | |
| 352 if (newDisplayItem.isCachedDrawing()) { | |
| 353 updatedList.appendByMoving(*currentIt); | |
| 354 ++currentIt; | |
| 355 } else { | |
| 356 ASSERT(newDisplayItem.isCachedSubsequence()); | |
| 357 copyCachedSubsequence(currentIt, updatedList); | |
| 358 ASSERT(updatedList.last().isEndSubsequence()); | |
| 359 } | |
| 360 } else { | |
| 361 ASSERT(!newDisplayItem.isDrawing() | |
| 362 || newDisplayItem.skippedCache() | |
| 363 || !clientCacheIsValid(newDisplayItem.client()) | |
| 364 || (RuntimeEnabledFeatures::slimmingPaintOffsetCachingEnabled()
&& paintOffsetWasInvalidated(newDisplayItem.client()))); | |
| 365 | |
| 366 updatedList.appendByMoving(*newIt); | |
| 367 | |
| 368 if (isSynchronized) | |
| 369 ++currentIt; | |
| 370 } | |
| 371 // Items before currentIt should have been copied so we don't need to in
dex them. | |
| 372 if (currentIt - outOfOrderIndexContext.nextItemToIndex > 0) | |
| 373 outOfOrderIndexContext.nextItemToIndex = currentIt; | |
| 374 } | |
| 375 | |
| 376 #if ENABLE(ASSERT) | |
| 377 if (RuntimeEnabledFeatures::slimmingPaintUnderInvalidationCheckingEnabled()) | |
| 378 checkNoRemainingCachedDisplayItems(); | |
| 379 #endif // ENABLE(ASSERT) | |
| 380 | |
| 381 | |
| 382 // TODO(jbroman): When subsequence caching applies to SPv2, we'll need to | |
| 383 // merge the paint chunks as well. | |
| 384 m_currentPaintArtifact.displayItems().swap(updatedList); | |
| 385 m_currentPaintArtifact.paintChunks() = m_newPaintChunks.releasePaintChunks()
; | |
| 386 | |
| 387 m_newDisplayItems.clear(); | |
| 388 m_validlyCachedClientsDirty = true; | |
| 389 m_numCachedItems = 0; | |
| 390 } | |
| 391 | |
| 392 size_t DisplayItemList::approximateUnsharedMemoryUsage() const | |
| 393 { | |
| 394 size_t memoryUsage = sizeof(*this); | |
| 395 | |
| 396 // Memory outside this class due to m_currentPaintArtifact. | |
| 397 memoryUsage += m_currentPaintArtifact.approximateUnsharedMemoryUsage() - siz
eof(m_currentPaintArtifact); | |
| 398 | |
| 399 // TODO(jbroman): If display items begin to have significant external memory | |
| 400 // usage that's not shared with the embedder, we should account for it here. | |
| 401 // | |
| 402 // External objects, shared with the embedder, such as SkPicture, should be | |
| 403 // excluded to avoid double counting. It is the embedder's responsibility to | |
| 404 // count such objects. | |
| 405 // | |
| 406 // At time of writing, the only known case of unshared external memory was | |
| 407 // the rounded clips vector in ClipDisplayItem, which is not expected to | |
| 408 // contribute significantly to memory usage. | |
| 409 | |
| 410 // Memory outside this class due to m_newDisplayItems. | |
| 411 ASSERT(m_newDisplayItems.isEmpty()); | |
| 412 memoryUsage += m_newDisplayItems.memoryUsageInBytes(); | |
| 413 | |
| 414 return memoryUsage; | |
| 415 } | |
| 416 | |
| 417 void DisplayItemList::updateValidlyCachedClientsIfNeeded() const | |
| 418 { | |
| 419 if (!m_validlyCachedClientsDirty) | |
| 420 return; | |
| 421 | |
| 422 m_validlyCachedClients.clear(); | |
| 423 m_validlyCachedClientsDirty = false; | |
| 424 | |
| 425 DisplayItemClient lastAddedClient = nullptr; | |
| 426 for (const DisplayItem& displayItem : m_currentPaintArtifact.displayItems())
{ | |
| 427 if (displayItem.client() == lastAddedClient) | |
| 428 continue; | |
| 429 if (displayItem.isCacheable()) { | |
| 430 lastAddedClient = displayItem.client(); | |
| 431 m_validlyCachedClients.add(lastAddedClient); | |
| 432 } | |
| 433 } | |
| 434 } | |
| 435 | |
| 436 #if ENABLE(ASSERT) | |
| 437 | |
| 438 void DisplayItemList::checkUnderInvalidation(DisplayItems::iterator& newIt, Disp
layItems::iterator& currentIt) | |
| 439 { | |
| 440 ASSERT(RuntimeEnabledFeatures::slimmingPaintUnderInvalidationCheckingEnabled
()); | |
| 441 ASSERT(newIt->isCached()); | |
| 442 | |
| 443 // When under-invalidation-checking is enabled, the forced painting is follo
wing the cached display item. | |
| 444 DisplayItem::Type nextItemType = DisplayItem::nonCachedType(newIt->type()); | |
| 445 ++newIt; | |
| 446 ASSERT(newIt->type() == nextItemType); | |
| 447 | |
| 448 if (newIt->isDrawing()) { | |
| 449 checkCachedDisplayItemIsUnchanged("", *newIt, *currentIt); | |
| 450 return; | |
| 451 } | |
| 452 | |
| 453 ASSERT(newIt->isSubsequence()); | |
| 454 | |
| 455 #ifndef NDEBUG | |
| 456 CString messagePrefix = String::format("(In CachedSubsequence of %s)", newIt
->clientDebugString().utf8().data()).utf8(); | |
| 457 #else | |
| 458 CString messagePrefix = "(In CachedSubsequence)"; | |
| 459 #endif | |
| 460 | |
| 461 DisplayItem::Id endSubsequenceId(newIt->client(), DisplayItem::subsequenceTy
peToEndSubsequenceType(newIt->type()), 0); | |
| 462 while (true) { | |
| 463 ASSERT(newIt != m_newDisplayItems.end()); | |
| 464 if (newIt->isCached()) | |
| 465 checkUnderInvalidation(newIt, currentIt); | |
| 466 else | |
| 467 checkCachedDisplayItemIsUnchanged(messagePrefix.data(), *newIt, *cur
rentIt); | |
| 468 | |
| 469 if (endSubsequenceId.matches(*newIt)) | |
| 470 break; | |
| 471 | |
| 472 ++newIt; | |
| 473 ++currentIt; | |
| 474 } | |
| 475 } | |
| 476 | |
| 477 static void showUnderInvalidationError(const char* messagePrefix, const char* re
ason, const DisplayItem* newItem, const DisplayItem* oldItem) | |
| 478 { | |
| 479 #ifndef NDEBUG | |
| 480 WTFLogAlways("%s %s:\nNew display item: %s\nOld display item: %s\nSee http:/
/crbug.com/450725.", messagePrefix, reason, | |
| 481 newItem ? newItem->asDebugString().utf8().data() : "None", | |
| 482 oldItem ? oldItem->asDebugString().utf8().data() : "None"); | |
| 483 #else | |
| 484 WTFLogAlways("%s %s. Run debug build to get more details\nSee http://crbug.c
om/450725.", messagePrefix, reason); | |
| 485 #endif // NDEBUG | |
| 486 } | |
| 487 | |
| 488 void DisplayItemList::checkCachedDisplayItemIsUnchanged(const char* messagePrefi
x, const DisplayItem& newItem, const DisplayItem& oldItem) | |
| 489 { | |
| 490 ASSERT(RuntimeEnabledFeatures::slimmingPaintUnderInvalidationCheckingEnabled
()); | |
| 491 ASSERT(!newItem.isCached()); | |
| 492 ASSERT(!oldItem.isCached()); | |
| 493 | |
| 494 if (newItem.skippedCache()) { | |
| 495 showUnderInvalidationError(messagePrefix, "ERROR: under-invalidation: sk
ipped-cache in cached subsequence", &newItem, &oldItem); | |
| 496 ASSERT_NOT_REACHED(); | |
| 497 } | |
| 498 | |
| 499 if (newItem.isCacheable() && !m_validlyCachedClients.contains(newItem.client
())) { | |
| 500 showUnderInvalidationError(messagePrefix, "ERROR: under-invalidation: in
validated in cached subsequence", &newItem, &oldItem); | |
| 501 ASSERT_NOT_REACHED(); | |
| 502 } | |
| 503 | |
| 504 if (newItem.equals(oldItem)) | |
| 505 return; | |
| 506 | |
| 507 showUnderInvalidationError(messagePrefix, "ERROR: under-invalidation: displa
y item changed", &newItem, &oldItem); | |
| 508 | |
| 509 #ifndef NDEBUG | |
| 510 if (newItem.isDrawing()) { | |
| 511 RefPtr<const SkPicture> newPicture = static_cast<const DrawingDisplayIte
m&>(newItem).picture(); | |
| 512 RefPtr<const SkPicture> oldPicture = static_cast<const DrawingDisplayIte
m&>(oldItem).picture(); | |
| 513 String oldPictureDebugString = oldPicture ? pictureAsDebugString(oldPict
ure.get()) : "None"; | |
| 514 String newPictureDebugString = newPicture ? pictureAsDebugString(newPict
ure.get()) : "None"; | |
| 515 WTFLogAlways("old picture:\n%s\n", oldPictureDebugString.utf8().data()); | |
| 516 WTFLogAlways("new picture:\n%s\n", newPictureDebugString.utf8().data()); | |
| 517 } | |
| 518 #endif // NDEBUG | |
| 519 | |
| 520 ASSERT_NOT_REACHED(); | |
| 521 } | |
| 522 | |
| 523 void DisplayItemList::checkNoRemainingCachedDisplayItems() | |
| 524 { | |
| 525 ASSERT(RuntimeEnabledFeatures::slimmingPaintUnderInvalidationCheckingEnabled
()); | |
| 526 | |
| 527 for (const auto& displayItem : m_currentPaintArtifact.displayItems()) { | |
| 528 if (!displayItem.isValid() || !displayItem.isCacheable() || !clientCache
IsValid(displayItem.client())) | |
| 529 continue; | |
| 530 showUnderInvalidationError("", "May be under-invalidation: no new displa
y item", nullptr, &displayItem); | |
| 531 } | |
| 532 } | |
| 533 | |
| 534 #endif // ENABLE(ASSERT) | |
| 535 | |
| 536 #ifndef NDEBUG | |
| 537 | |
| 538 WTF::String DisplayItemList::displayItemsAsDebugString(const DisplayItems& list)
const | |
| 539 { | |
| 540 StringBuilder stringBuilder; | |
| 541 size_t i = 0; | |
| 542 for (auto it = list.begin(); it != list.end(); ++it, ++i) { | |
| 543 const DisplayItem& displayItem = *it; | |
| 544 if (i) | |
| 545 stringBuilder.append(",\n"); | |
| 546 stringBuilder.append(String::format("{index: %d, ", (int)i)); | |
| 547 displayItem.dumpPropertiesAsDebugString(stringBuilder); | |
| 548 if (displayItem.isValid()) { | |
| 549 stringBuilder.append(", cacheIsValid: "); | |
| 550 stringBuilder.append(clientCacheIsValid(displayItem.client()) ? "tru
e" : "false"); | |
| 551 } | |
| 552 stringBuilder.append('}'); | |
| 553 } | |
| 554 return stringBuilder.toString(); | |
| 555 } | |
| 556 | |
| 557 void DisplayItemList::showDebugData() const | |
| 558 { | |
| 559 WTFLogAlways("current display items: [%s]\n", displayItemsAsDebugString(m_cu
rrentPaintArtifact.displayItems()).utf8().data()); | |
| 560 WTFLogAlways("new display items: [%s]\n", displayItemsAsDebugString(m_newDis
playItems).utf8().data()); | |
| 561 } | |
| 562 | |
| 563 #endif // ifndef NDEBUG | |
| 564 | |
| 565 } // namespace blink | |
| OLD | NEW |