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
|
// Copyright 2016 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 "content/common/gpu/ca_layer_partial_damage_tree_mac.h"
#include "base/command_line.h"
#include "base/mac/scoped_nsobject.h"
#include "base/mac/sdk_forward_declarations.h"
#include "base/trace_event/trace_event.h"
#include "ui/base/ui_base_switches.h"
#include "ui/gfx/transform.h"
@interface CALayer(Private)
-(void)setContentsChanged;
@end
namespace content {
namespace {
// When selecting a CALayer to re-use for partial damage, this is the maximum
// fraction of the merged layer's pixels that may be not-updated by the swap
// before we consider the CALayer to not be a good enough match, and create a
// new one.
const float kMaximumPartialDamageWasteFraction = 1.2f;
// The maximum number of partial damage layers that may be created before we
// give up and remove them all (doing full damage in the process).
const size_t kMaximumPartialDamageLayers = 8;
} // namespace
class CALayerPartialDamageTree::OverlayPlane {
public:
OverlayPlane(base::ScopedCFTypeRef<IOSurfaceRef> io_surface,
const gfx::Rect& pixel_frame_rect,
const gfx::RectF& contents_rect)
: io_surface(io_surface),
contents_rect(contents_rect),
pixel_frame_rect(pixel_frame_rect),
layer_needs_update(true) {}
~OverlayPlane() {
[ca_layer setContents:nil];
[ca_layer removeFromSuperlayer];
ca_layer.reset();
}
const base::ScopedCFTypeRef<IOSurfaceRef> io_surface;
const gfx::RectF contents_rect;
const gfx::Rect pixel_frame_rect;
bool layer_needs_update;
base::scoped_nsobject<CALayer> ca_layer;
void TakeCALayerFrom(OverlayPlane* other_plane) {
ca_layer.swap(other_plane->ca_layer);
}
void UpdateProperties(float scale_factor) {
if (layer_needs_update) {
[ca_layer setOpaque:YES];
id new_contents = static_cast<id>(io_surface.get());
if ([ca_layer contents] == new_contents)
[ca_layer setContentsChanged];
else
[ca_layer setContents:new_contents];
[ca_layer setContentsRect:contents_rect.ToCGRect()];
[ca_layer setAnchorPoint:CGPointZero];
if ([ca_layer respondsToSelector:(@selector(setContentsScale:))])
[ca_layer setContentsScale:scale_factor];
gfx::RectF dip_frame_rect = gfx::RectF(pixel_frame_rect);
dip_frame_rect.Scale(1 / scale_factor);
[ca_layer setBounds:CGRectMake(0, 0, dip_frame_rect.width(),
dip_frame_rect.height())];
[ca_layer
setPosition:CGPointMake(dip_frame_rect.x(), dip_frame_rect.y())];
}
static bool show_borders =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kShowMacOverlayBorders);
if (show_borders) {
base::ScopedCFTypeRef<CGColorRef> color;
if (!layer_needs_update) {
// Green represents contents that are unchanged across frames.
color.reset(CGColorCreateGenericRGB(0, 1, 0, 1));
} else {
// Red represents damaged contents.
color.reset(CGColorCreateGenericRGB(1, 0, 0, 1));
}
[ca_layer setBorderWidth:1];
[ca_layer setBorderColor:color];
}
layer_needs_update = false;
}
private:
};
void CALayerPartialDamageTree::UpdatePartialDamagePlanes(
CALayerPartialDamageTree* old_tree,
const gfx::Rect& pixel_damage_rect) {
// Don't create partial damage layers if partial swap is disabled.
if (!allow_partial_swap_)
return;
// Only create partial damage layers when building on top of an existing tree.
if (!old_tree)
return;
// If the frame size has changed, discard all of the old partial damage
// layers.
if (old_tree->root_plane_->pixel_frame_rect != root_plane_->pixel_frame_rect)
return;
// If there is full damage, discard all of the old partial damage layers.
if (pixel_damage_rect == root_plane_->pixel_frame_rect)
return;
// If there is no damage, don't change anything.
if (pixel_damage_rect.IsEmpty()) {
std::swap(partial_damage_planes_, old_tree->partial_damage_planes_);
return;
}
// Find the last partial damage plane to re-use the CALayer from. Grow the
// new rect for this layer to include this damage, and all nearby partial
// damage layers.
scoped_ptr<OverlayPlane> plane_for_swap;
{
auto plane_to_reuse_iter = old_tree->partial_damage_planes_.end();
gfx::Rect plane_to_reuse_enlarged_pixel_damage_rect;
for (auto old_plane_iter = old_tree->partial_damage_planes_.begin();
old_plane_iter != old_tree->partial_damage_planes_.end();
++old_plane_iter) {
gfx::Rect enlarged_pixel_damage_rect =
(*old_plane_iter)->pixel_frame_rect;
enlarged_pixel_damage_rect.Union(pixel_damage_rect);
// Compute the fraction of the pixels that would not be updated by this
// swap. If it is too big, try another layer.
float waste_fraction = enlarged_pixel_damage_rect.size().GetArea() * 1.f /
pixel_damage_rect.size().GetArea();
if (waste_fraction > kMaximumPartialDamageWasteFraction)
continue;
plane_to_reuse_iter = old_plane_iter;
plane_to_reuse_enlarged_pixel_damage_rect.Union(
enlarged_pixel_damage_rect);
}
if (plane_to_reuse_iter != old_tree->partial_damage_planes_.end()) {
gfx::RectF enlarged_contents_rect =
gfx::RectF(plane_to_reuse_enlarged_pixel_damage_rect);
enlarged_contents_rect.Scale(1. / root_plane_->pixel_frame_rect.width(),
1. / root_plane_->pixel_frame_rect.height());
plane_for_swap.reset(new OverlayPlane(
root_plane_->io_surface, plane_to_reuse_enlarged_pixel_damage_rect,
enlarged_contents_rect));
plane_for_swap->TakeCALayerFrom((*plane_to_reuse_iter).get());
if (*plane_to_reuse_iter != old_tree->partial_damage_planes_.back()) {
CALayer* superlayer = [plane_for_swap->ca_layer superlayer];
[plane_for_swap->ca_layer removeFromSuperlayer];
[superlayer addSublayer:plane_for_swap->ca_layer];
}
}
}
// If we haven't found an appropriate layer to re-use, create a new one, if
// we haven't already created too many.
if (!plane_for_swap.get() &&
old_tree->partial_damage_planes_.size() < kMaximumPartialDamageLayers) {
gfx::RectF contents_rect = gfx::RectF(pixel_damage_rect);
contents_rect.Scale(1. / root_plane_->pixel_frame_rect.width(),
1. / root_plane_->pixel_frame_rect.height());
plane_for_swap.reset(new OverlayPlane(root_plane_->io_surface,
pixel_damage_rect, contents_rect));
}
// And if we still don't have a layer, do full damage.
if (!plane_for_swap.get())
return;
// Walk all old partial damage planes. Remove anything that is now completely
// covered, and move everything else into the new |partial_damage_planes_|.
for (auto& old_plane : old_tree->partial_damage_planes_) {
if (!old_plane.get())
continue;
// Intersect the planes' frames with the new root plane to ensure that
// they don't get kept alive inappropriately.
gfx::Rect old_plane_frame_rect = old_plane->pixel_frame_rect;
old_plane_frame_rect.Intersect(root_plane_->pixel_frame_rect);
bool old_plane_covered_by_swap = false;
if (plane_for_swap.get() &&
plane_for_swap->pixel_frame_rect.Contains(old_plane_frame_rect)) {
old_plane_covered_by_swap = true;
}
if (!old_plane_covered_by_swap) {
DCHECK(old_plane->ca_layer);
partial_damage_planes_.push_back(std::move(old_plane));
}
}
partial_damage_planes_.push_back(std::move(plane_for_swap));
}
void CALayerPartialDamageTree::UpdateRootAndPartialDamagePlanes(
scoped_ptr<CALayerPartialDamageTree> old_tree,
const gfx::Rect& pixel_damage_rect) {
// First update the partial damage tree.
UpdatePartialDamagePlanes(old_tree.get(), pixel_damage_rect);
if (old_tree) {
if (partial_damage_planes_.empty()) {
// If there are no partial damage planes, then we will be updating the
// root layer. Take the CALayer from the old tree.
root_plane_->TakeCALayerFrom(old_tree->root_plane_.get());
} else {
// If there is a partial damage tree, then just take the old plane
// from the previous frame, so that there is no update to it.
root_plane_.swap(old_tree->root_plane_);
}
}
}
void CALayerPartialDamageTree::UpdateCALayers(CALayer* superlayer,
float scale_factor) {
if (!allow_partial_swap_) {
DCHECK(partial_damage_planes_.empty());
return;
}
// Allocate and update CALayers for the backbuffer and partial damage layers.
if (!root_plane_->ca_layer) {
DCHECK(partial_damage_planes_.empty());
root_plane_->ca_layer.reset([[CALayer alloc] init]);
[superlayer setSublayers:nil];
[superlayer addSublayer:root_plane_->ca_layer];
}
// Excessive logging to debug white screens (crbug.com/583805).
// TODO(ccameron): change this back to a DLOG.
if ([root_plane_->ca_layer superlayer] != superlayer) {
LOG(ERROR) << "CALayerPartialDamageTree root layer not attached to tree.";
}
for (auto& plane : partial_damage_planes_) {
if (!plane->ca_layer) {
DCHECK(plane == partial_damage_planes_.back());
plane->ca_layer.reset([[CALayer alloc] init]);
}
if (![plane->ca_layer superlayer]) {
DCHECK(plane == partial_damage_planes_.back());
[superlayer addSublayer:plane->ca_layer];
}
}
root_plane_->UpdateProperties(scale_factor);
for (auto& plane : partial_damage_planes_)
plane->UpdateProperties(scale_factor);
}
CALayerPartialDamageTree::CALayerPartialDamageTree(
bool allow_partial_swap,
base::ScopedCFTypeRef<IOSurfaceRef> io_surface,
const gfx::Rect& pixel_frame_rect)
: allow_partial_swap_(allow_partial_swap) {
root_plane_.reset(
new OverlayPlane(io_surface, pixel_frame_rect, gfx::RectF(0, 0, 1, 1)));
}
CALayerPartialDamageTree::~CALayerPartialDamageTree() {}
base::ScopedCFTypeRef<IOSurfaceRef>
CALayerPartialDamageTree::RootLayerIOSurface() {
return root_plane_->io_surface;
}
void CALayerPartialDamageTree::CommitCALayers(
CALayer* superlayer,
scoped_ptr<CALayerPartialDamageTree> old_tree,
float scale_factor,
const gfx::Rect& pixel_damage_rect) {
TRACE_EVENT0("gpu", "CALayerPartialDamageTree::CommitCALayers");
UpdateRootAndPartialDamagePlanes(std::move(old_tree), pixel_damage_rect);
UpdateCALayers(superlayer, scale_factor);
}
} // namespace content
|