File: raster_invalidator.cc

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (442 lines) | stat: -rw-r--r-- 17,881 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/platform/graphics/paint/raster_invalidator.h"

#include <algorithm>
#include <memory>
#include <utility>

#include "third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.h"
#include "third_party/blink/renderer/platform/graphics/paint/display_item_raster_invalidator.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_artifact.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "ui/gfx/geometry/skia_conversions.h"

namespace blink {

void RasterInvalidator::Trace(Visitor* visitor) const {
  visitor->Trace(layer_state_);
  visitor->Trace(current_paint_artifact_);
  visitor->Trace(old_paint_artifact_);
  visitor->Trace(tracking_);
}

void RasterInvalidator::SetTracksRasterInvalidations(bool should_track) {
  if (should_track) {
    if (!tracking_) {
      tracking_ = MakeGarbageCollected<RasterInvalidationTracking>();
    }
    tracking_->ClearInvalidations();
  } else if (!RasterInvalidationTracking::ShouldAlwaysTrack()) {
    tracking_ = nullptr;
  } else if (tracking_) {
    tracking_->ClearInvalidations();
  }
}

const PaintChunk& RasterInvalidator::GetOldChunk(wtf_size_t index) const {
  DCHECK(old_paint_artifact_);
  const auto& old_chunk_info = old_paint_chunks_info_[index];
  const auto& old_chunk =
      old_paint_artifact_
          ->GetPaintChunks()[old_chunk_info.index_in_paint_artifact];
#if DCHECK_IS_ON()
  DCHECK_EQ(old_chunk.id, old_chunk_info.id);
#endif
  return old_chunk;
}

wtf_size_t RasterInvalidator::MatchNewChunkToOldChunk(
    const PaintChunk& new_chunk,
    wtf_size_t old_index) const {
  if (!new_chunk.CanMatchOldChunk())
    return kNotFound;

  for (wtf_size_t i = old_index; i < old_paint_chunks_info_.size(); i++) {
    if (new_chunk.Matches(GetOldChunk(i)))
      return i;
  }
  return kNotFound;
}

PaintInvalidationReason RasterInvalidator::ChunkPropertiesChanged(
    const PaintChunk& new_chunk,
    const PaintChunk& old_chunk,
    const PaintChunkInfo& new_chunk_info,
    const PaintChunkInfo& old_chunk_info,
    const PropertyTreeState& layer_state,
    const float absolute_translation_tolerance,
    const float other_transform_tolerance) const {
  if (new_chunk.effectively_invisible != old_chunk.effectively_invisible)
    return PaintInvalidationReason::kPaintProperty;

  // Special case for transform changes because we may create or delete some
  // transform nodes when no raster invalidation is needed. For example, when
  // a composited layer previously not transformed now gets transformed.
  // Check for real accumulated transform change instead.
  if (!new_chunk_info.chunk_to_layer_transform.ApproximatelyEqual(
          old_chunk_info.chunk_to_layer_transform,
          absolute_translation_tolerance, other_transform_tolerance,
          other_transform_tolerance)) {
    return PaintInvalidationReason::kPaintProperty;
  }

  // Treat the chunk property as changed if the effect node pointer is
  // different, or the effect node's value changed between the layer state and
  // the chunk state.
  const auto& new_chunk_state = new_chunk.properties;
  bool clip_node_is_different = false;
  bool effect_node_is_different = false;
  if (!new_chunk.is_moved_from_cached_subsequence) {
    clip_node_is_different =
        &new_chunk_state.Clip() != &old_chunk.properties.Clip();
    effect_node_is_different =
        &new_chunk_state.Effect() != &old_chunk.properties.Effect();
  }
  if (effect_node_is_different ||
      new_chunk_state.Effect().Changed(
          PaintPropertyChangeType::kChangedOnlySimpleValues, layer_state,
          &new_chunk_state.Transform())) {
    return PaintInvalidationReason::kPaintProperty;
  }

  // Check for accumulated clip rect change, if the clip rects are tight.
  if (new_chunk_info.chunk_to_layer_clip.IsTight() &&
      old_chunk_info.chunk_to_layer_clip.IsTight()) {
    gfx::RectF new_clip_rect = new_chunk_info.chunk_to_layer_clip.Rect();
    gfx::RectF old_clip_rect = old_chunk_info.chunk_to_layer_clip.Rect();
    if (new_clip_rect == old_clip_rect)
      return PaintInvalidationReason::kNone;
    // Ignore differences out of the current layer bounds.
    gfx::RectF new_clip_in_layer_bounds = ClipByLayerBounds(new_clip_rect);
    gfx::RectF old_clip_in_layer_bounds = ClipByLayerBounds(old_clip_rect);
    if (new_clip_in_layer_bounds == old_clip_in_layer_bounds)
      return PaintInvalidationReason::kNone;

    // Clip changed and may have visual effect, so we need raster invalidation.
    if (!new_clip_in_layer_bounds.Contains(
            gfx::RectF(new_chunk_info.bounds_in_layer)) ||
        !old_clip_in_layer_bounds.Contains(
            gfx::RectF(old_chunk_info.bounds_in_layer))) {
      // If the chunk is not fully covered by the clip rect, we have to do full
      // invalidation instead of incremental because the delta parts of the
      // layer bounds may not cover all changes caused by the clip change.
      // This can happen because of pixel snapping, raster effect outset, etc.
      return PaintInvalidationReason::kPaintProperty;
    }
    // Otherwise we just invalidate the delta parts of the layer bounds.
    return PaintInvalidationReason::kIncremental;
  }

  // Otherwise treat the chunk property as changed if the clip node pointer is
  // different, or the clip node's value changed between the layer state and the
  // chunk state.
  if (clip_node_is_different ||
      new_chunk_state.Clip().Changed(
          PaintPropertyChangeType::kChangedOnlySimpleValues, layer_state,
          &new_chunk_state.Transform()))
    return PaintInvalidationReason::kPaintProperty;

  return PaintInvalidationReason::kNone;
}

static bool ShouldSkipForRasterInvalidation(
    const PaintChunkIterator& chunk_it,
    const EffectPaintPropertyNode* parent_effect) {
  if (!chunk_it->DrawsContent()) {
    if (RuntimeEnabledFeatures::EmptyReferenceFilterInvalidationEnabled()) {
      // Skipping an empty chunk with a reference filter can lead to under
      // invalidation when the effect is decomposed. To avoid that, we override
      // the result of DrawsContent() in that scenario. Note that this isn't
      // always optimal as a chunk that is effectively_invisible (near-zero
      // opacity) will return false for DrawsContent() and this check will
      // override it.
      bool has_reference_filter = false;
      for (const auto* node = &chunk_it->properties.Effect().Unalias();
           node != parent_effect; node = node->UnaliasedParent()) {
        if (node->HasReferenceFilter()) {
          has_reference_filter = true;
          break;
        }
      }
      if (!has_reference_filter) {
        return true;
      }
    } else {
      return true;
    }
  }

  // Foreign layers take care of raster invalidation by themselves.
  if (DisplayItem::IsForeignLayerType(chunk_it->id.type))
    return true;

  return false;
}

static bool ScrollbarNeedsUpdateDisplay(const PaintChunkIterator& chunk_it) {
  if (chunk_it->size() != 1) {
    return false;
  }
  if (auto* scrollbar =
          DynamicTo<ScrollbarDisplayItem>(*chunk_it.DisplayItems().begin())) {
    return scrollbar->NeedsUpdateDisplay();
  }
  return false;
}

// Generates raster invalidations by checking changes (appearing, disappearing,
// reordering, property changes) of chunks. The logic is similar to
// PaintController::GenerateRasterInvalidations(). The complexity is between
// O(n) and O(m*n) where m and n are the numbers of old and new chunks,
// respectively. Normally both m and n are small numbers. The best case is that
// all old chunks have matching new chunks in the same order. The worst case is
// that no matching chunks except the first one (which always matches otherwise
// we won't reuse the RasterInvalidator), which is rare. In
// common cases that most of the chunks can be matched in-order, the complexity
// is slightly larger than O(n).
void RasterInvalidator::GenerateRasterInvalidations(
    const PaintChunkSubset& new_chunks,
    bool layer_offset_or_state_changed,
    bool layer_effect_changed,
    Vector<PaintChunkInfo>& new_chunks_info) {
  ChunkToLayerMapper mapper(PropertyTreeState(layer_state_), layer_offset_);
  Vector<bool> old_chunks_matched;
  old_chunks_matched.resize(old_paint_chunks_info_.size());
  wtf_size_t old_index = 0;

  const float absolute_translation_tolerance = 1e-2f;
  const float other_transform_tolerance = 1e-4f;

  for (auto it = new_chunks.begin(); it != new_chunks.end(); ++it) {
    if (ShouldSkipForRasterInvalidation(it, &layer_state_.Effect())) {
      continue;
    }

    const auto& new_chunk = *it;
    auto matched_old_index = MatchNewChunkToOldChunk(new_chunk, old_index);
    if (matched_old_index == kNotFound) {
      // The new chunk doesn't match any old chunk.
      mapper.SwitchToChunk(new_chunk);
      auto& new_chunk_info = new_chunks_info.emplace_back(*this, mapper, it);
      AddRasterInvalidation(
          new_chunk_info.bounds_in_layer, new_chunk.id.client_id,
          new_chunk.is_cacheable ? PaintInvalidationReason::kChunkAppeared
                                 : PaintInvalidationReason::kChunkUncacheable,
          kClientIsNew);
      continue;
    }

    DCHECK(!old_chunks_matched[matched_old_index]);
    old_chunks_matched[matched_old_index] = true;

    auto& old_chunk_info = old_paint_chunks_info_[matched_old_index];
    const auto& old_chunk = GetOldChunk(matched_old_index);
    // Clip the old chunk bounds by the new layer bounds.
    old_chunk_info.bounds_in_layer =
        ClipByLayerBounds(old_chunk_info.bounds_in_layer);

    auto reason = PaintInvalidationReason::kNone;
    if (ScrollbarNeedsUpdateDisplay(it)) {
      reason = PaintInvalidationReason::kScrollControl;
    }

    // No need to invalidate if the chunk is moved from cached subsequence and
    // its paint properties didn't change relative to the layer.
    if (!layer_offset_or_state_changed &&
        reason == PaintInvalidationReason::kNone &&
        new_chunk.is_moved_from_cached_subsequence &&
        !new_chunk.properties.Changed(
            PaintPropertyChangeType::kChangedOnlySimpleValues,
            PropertyTreeState(layer_state_))) {
      new_chunks_info.emplace_back(old_chunk_info, it);
    } else {
      mapper.SwitchToChunk(new_chunk);
      auto& new_chunk_info = new_chunks_info.emplace_back(*this, mapper, it);

      if (reason == PaintInvalidationReason::kNone) {
        if (layer_effect_changed) {
          // Because of DecompositeEffect, the layer's effect may have changed
          // even if the chunk's didn't.
          reason = PaintInvalidationReason::kPaintProperty;
        } else {
          reason = ChunkPropertiesChanged(
              new_chunk, old_chunk, new_chunk_info, old_chunk_info,
              PropertyTreeState(layer_state_), absolute_translation_tolerance,
              other_transform_tolerance);
        }
      }

      if (IsFullPaintInvalidationReason(reason)) {
        // Invalidate both old and new bounds of the chunk if the chunk's paint
        // properties changed, or is moved backward and may expose area that was
        // previously covered by it.
        AddRasterInvalidation(old_chunk_info.bounds_in_layer,
                              new_chunk.id.client_id, reason, kClientIsNew);
        if (old_chunk_info.bounds_in_layer != new_chunk_info.bounds_in_layer) {
          AddRasterInvalidation(new_chunk_info.bounds_in_layer,
                                new_chunk.id.client_id, reason, kClientIsNew);
        }
        // Ignore the display item raster invalidations because we have fully
        // invalidated the chunk.
      } else {
        // We may have ignored tiny changes of transform, in which case we
        // should use the old chunk_to_layer_transform for later comparison to
        // correctly invalidate animating transform in tiny increments when the
        // accumulated change exceeds the tolerance.
        new_chunk_info.chunk_to_layer_transform =
            old_chunk_info.chunk_to_layer_transform;

        if (reason == PaintInvalidationReason::kIncremental) {
          IncrementallyInvalidateChunk(old_chunk_info, new_chunk_info,
                                       new_chunk.id.client_id);
        }

        if (&new_chunks.GetPaintArtifact() != old_paint_artifact_ &&
            !new_chunk.is_moved_from_cached_subsequence) {
          DisplayItemRasterInvalidator(
              *this,
              old_paint_artifact_->DisplayItemsInChunk(
                  old_chunk_info.index_in_paint_artifact),
              it.DisplayItems(), mapper)
              .Generate();
        }
      }
    }

    old_index = matched_old_index + 1;
  }

  // Invalidate remaining unmatched (disappeared or uncacheable) old chunks.
  for (wtf_size_t i = 0; i < old_paint_chunks_info_.size(); ++i) {
    if (old_chunks_matched[i])
      continue;

    const auto& old_chunk = GetOldChunk(i);
    auto reason = old_chunk.is_cacheable
                      ? PaintInvalidationReason::kChunkDisappeared
                      : PaintInvalidationReason::kChunkUncacheable;
    AddRasterInvalidation(old_paint_chunks_info_[i].bounds_in_layer,
                          old_chunk.id.client_id, reason, kClientIsOld);
  }
}

void RasterInvalidator::IncrementallyInvalidateChunk(
    const PaintChunkInfo& old_chunk_info,
    const PaintChunkInfo& new_chunk_info,
    DisplayItemClientId client_id) {
  SkRegion diff(gfx::RectToSkIRect(old_chunk_info.bounds_in_layer));
  diff.op(gfx::RectToSkIRect(new_chunk_info.bounds_in_layer),
          SkRegion::kXOR_Op);
  for (SkRegion::Iterator it(diff); !it.done(); it.next()) {
    AddRasterInvalidation(gfx::SkIRectToRect(it.rect()), client_id,
                          PaintInvalidationReason::kIncremental, kClientIsNew);
  }
}

void RasterInvalidator::TrackRasterInvalidation(const gfx::Rect& rect,
                                                DisplayItemClientId client_id,
                                                PaintInvalidationReason reason,
                                                ClientIsOldOrNew old_or_new) {
  if (rect.IsEmpty()) {
    return;
  }
  DCHECK(tracking_);
  String debug_name = old_or_new == kClientIsOld
                          ? old_paint_artifact_->ClientDebugName(client_id)
                          : current_paint_artifact_->ClientDebugName(client_id);
  tracking_->AddInvalidation(client_id, debug_name, rect, reason);
}

RasterInvalidationTracking& RasterInvalidator::EnsureTracking() {
  if (!tracking_) {
    tracking_ = MakeGarbageCollected<RasterInvalidationTracking>();
  }
  return *tracking_;
}

void RasterInvalidator::Generate(
    const PaintChunkSubset& new_chunks,
    const gfx::Vector2dF& layer_offset,
    const gfx::Size& layer_bounds,
    const PropertyTreeState& layer_state) {
  if (RasterInvalidationTracking::ShouldAlwaysTrack())
    EnsureTracking();

  bool layer_offset_or_state_changed =
      layer_offset_ != layer_offset || layer_state_ != layer_state;
  bool layer_effect_changed = &layer_state_.Effect() != &layer_state.Effect();
  bool layer_bounds_was_empty = layer_bounds_.IsEmpty();
  layer_offset_ = layer_offset;
  layer_bounds_ = layer_bounds;
  layer_state_ = layer_state;
  current_paint_artifact_ = &new_chunks.GetPaintArtifact();

  Vector<PaintChunkInfo> new_chunks_info;
  new_chunks_info.reserve(new_chunks.size());

  if (layer_bounds_was_empty || layer_bounds_.IsEmpty()) {
    // Fast path if either the old bounds or the new bounds is empty. We still
    // need to update new_chunks_info for the next cycle.
    PopulatePaintChunksInfo(new_chunks, layer_offset, layer_state,
                            new_chunks_info);
    if (!layer_bounds.IsEmpty() && !new_chunks.IsEmpty()) {
      AddRasterInvalidation(gfx::Rect(layer_bounds),
                            new_chunks.begin()->id.client_id,
                            PaintInvalidationReason::kFullLayer, kClientIsNew);
    }
  } else {
    GenerateRasterInvalidations(new_chunks, layer_offset_or_state_changed,
                                layer_effect_changed, new_chunks_info);
  }

  old_paint_chunks_info_ = std::move(new_chunks_info);
  old_paint_artifact_ = &new_chunks.GetPaintArtifact();
  current_paint_artifact_ = nullptr;
}

void RasterInvalidator::UpdateForRasterInducingScroll(
    const PaintChunkSubset& chunks) {
  old_paint_chunks_info_.Shrink(0);
  PopulatePaintChunksInfo(chunks, layer_offset_,
                          PropertyTreeState(layer_state_),
                          old_paint_chunks_info_);
}

void RasterInvalidator::PopulatePaintChunksInfo(
    const PaintChunkSubset& chunks,
    const gfx::Vector2dF& layer_offset,
    const PropertyTreeState& layer_state,
    Vector<PaintChunkInfo>& chunks_info) {
  DCHECK(chunks_info.empty());
  ChunkToLayerMapper mapper(layer_state, layer_offset);
  for (auto it = chunks.begin(); it != chunks.end(); ++it) {
    if (ShouldSkipForRasterInvalidation(it, &layer_state.Effect())) {
      continue;
    }
    mapper.SwitchToChunk(*it);
    chunks_info.emplace_back(*this, mapper, it);
  }
}

void RasterInvalidator::SetOldPaintArtifact(
    const PaintArtifact& old_paint_artifact) {
  old_paint_artifact_ = &old_paint_artifact;
}

size_t RasterInvalidator::ApproximateUnsharedMemoryUsage() const {
  return sizeof(*this) +
         old_paint_chunks_info_.capacity() * sizeof(PaintChunkInfo);
}

void RasterInvalidator::ClearOldStates() {
  old_paint_artifact_ = nullptr;
  old_paint_chunks_info_.clear();
  layer_offset_ = gfx::Vector2dF();
  layer_bounds_ = gfx::Size();
}

}  // namespace blink