• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright 2020, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/**
18 * Utility class for deriving state and visibility from the hierarchy. This
19 * duplicates some of the logic in surface flinger. If the trace contains
20 * composition state (visibleRegion), it will be used otherwise it will be
21 * derived.
22 */
23import {multiply_rect, is_simple_rotation} from './matrix_utils.js';
24
25// Layer flags
26const FLAG_HIDDEN = 0x01;
27const FLAG_OPAQUE = 0x02;
28const FLAG_SECURE = 0x80;
29
30function flags_to_string(flags) {
31  if (!flags) return '';
32  const verboseFlags = [];
33  if (flags & FLAG_HIDDEN) verboseFlags.push('HIDDEN');
34  if (flags & FLAG_OPAQUE) verboseFlags.push('OPAQUE');
35  if (flags & FLAG_SECURE) verboseFlags.push('SECURE');
36  return verboseFlags.join('|') + ' (' + flags + ')';
37}
38
39function is_empty(region) {
40  return region == undefined ||
41      region.rect == undefined ||
42      region.rect.length == 0 ||
43      region.rect.every(function(r) {
44        return is_empty_rect(r);
45      } );
46}
47
48function is_empty_rect(rect) {
49  const right = rect.right || 0;
50  const left = rect.left || 0;
51  const top = rect.top || 0;
52  const bottom = rect.bottom || 0;
53
54  return (right - left) <= 0 || (bottom - top) <= 0;
55}
56
57function is_rect_empty_and_valid(rect) {
58  return rect &&
59    (rect.left - rect.right === 0 || rect.top - rect.bottom === 0);
60}
61
62/**
63 * The transformation matrix is defined as the product of:
64 * | cos(a) -sin(a) |  \/  | X 0 |
65 * | sin(a)  cos(a) |  /\  | 0 Y |
66 *
67 * where a is a rotation angle, and X and Y are scaling factors.
68 * A transformation matrix is invalid when either X or Y is zero,
69 * as a rotation matrix is valid for any angle. When either X or Y
70 * is 0, then the scaling matrix is not invertible, which makes the
71 * transformation matrix not invertible as well. A 2D matrix with
72 * components | A B | is not invertible if and only if AD - BC = 0.
73 *            | C D |
74 * This check is included above.
75 */
76function is_transform_invalid(transform) {
77  return !transform || (transform.dsdx * transform.dtdy ===
78      transform.dtdx * transform.dsdy); // determinant of transform
79}
80
81function is_opaque(layer) {
82  if (layer.color == undefined || layer.color.a == undefined || layer.color.a != 1) return false;
83  return layer.isOpaque;
84}
85
86function fills_color(layer) {
87  return layer.color && layer.color.a > 0 &&
88      layer.color.r >= 0 && layer.color.g >= 0 &&
89      layer.color.b >= 0;
90}
91
92function draws_shadows(layer) {
93  return layer.shadowRadius && layer.shadowRadius > 0;
94}
95
96function has_blur(layer) {
97  return layer.backgroundBlurRadius && layer.backgroundBlurRadius > 0;
98}
99
100function has_effects(layer) {
101  // Support previous color layer
102  if (layer.type === 'ColorLayer') return true;
103
104  // Support newer effect layer
105  return layer.type === 'EffectLayer' &&
106      (fills_color(layer) || draws_shadows(layer) || has_blur(layer));
107}
108
109function is_hidden_by_policy(layer) {
110  return layer.flags & FLAG_HIDDEN == FLAG_HIDDEN ||
111    // offscreen layer root has a unique layer id
112    layer.id == 0x7FFFFFFD;
113}
114
115/**
116 * Checks if the layer is visible based on its visibleRegion if available
117 * or its type, active buffer content, alpha and properties.
118 */
119function is_visible(layer, hiddenByPolicy, includesCompositionState) {
120  if (includesCompositionState) {
121    return !is_empty(layer.visibleRegion);
122  }
123
124  if (hiddenByPolicy) {
125    return false;
126  }
127
128  if (!layer.activeBuffer && !has_effects(layer)) {
129    return false;
130  }
131
132  if (!layer.color || !layer.color.a || layer.color.a == 0) {
133    return false;
134  }
135
136  if (layer.occludedBy && layer.occludedBy.length > 0) {
137    return false;
138  }
139
140  if (!layer.bounds || is_empty_rect(layer.bounds)) {
141    return false;
142  }
143
144  return true;
145}
146
147function get_visibility_reason(layer, includesCompositionState) {
148  if (layer.type === 'ContainerLayer') {
149    return 'ContainerLayer';
150  }
151
152  if (is_hidden_by_policy(layer)) {
153    return 'Flag is hidden';
154  }
155
156  if (layer.hidden) {
157    return 'Hidden by parent';
158  }
159
160  const isBufferLayer = (layer.type === 'BufferStateLayer' ||
161      layer.type === 'BufferQueueLayer');
162  if (isBufferLayer && (!layer.activeBuffer ||
163    layer.activeBuffer.height === 0 || layer.activeBuffer.width === 0)) {
164    return 'Buffer is empty';
165  }
166
167  if (!layer.color || !layer.color.a || layer.color.a == 0) {
168    return 'Alpha is 0';
169  }
170
171  if (is_rect_empty_and_valid(layer.crop)) {
172    return 'Crop is 0x0';
173  }
174
175  if (!layer.bounds || is_empty_rect(layer.bounds)) {
176    return 'Bounds is 0x0';
177  }
178
179  if (is_transform_invalid(layer.transform)) {
180    return 'Transform is invalid';
181  }
182  if (layer.isRelativeOf && layer.zOrderRelativeOf == -1) {
183    return 'RelativeOf layer has been removed';
184  }
185
186  const isEffectLayer = (layer.type === 'EffectLayer');
187  if (isEffectLayer && !fills_color(layer) &&
188      !draws_shadows(layer) && !has_blur(layer)) {
189    return 'Effect layer does not have color fill, shadow or blur';
190  }
191
192  if (layer.occludedBy && layer.occludedBy.length > 0) {
193    return 'Layer is occluded by:' + layer.occludedBy.join();
194  }
195
196  if (includesCompositionState && is_empty(layer.visibleRegion)) {
197    return 'Visible region calculated by Composition Engine is empty';
198  }
199
200  if (layer.visible) {
201    return 'Unknown';
202  };
203}
204
205// Returns true if rectA overlaps rectB
206function overlaps(rectA, rectB) {
207  return rectA.left < rectB.right && rectA.right > rectB.left &&
208      rectA.top < rectB.bottom && rectA.bottom > rectA.top;
209}
210
211// Returns true if outer rect contains inner rect
212function contains(outerLayer, innerLayer) {
213  if (!is_simple_rotation(outerLayer.transform) ||
214      !is_simple_rotation(innerLayer.transform)) {
215    return false;
216  }
217  const outer = screen_bounds(outerLayer);
218  const inner = screen_bounds(innerLayer);
219  return inner.left >= outer.left && inner.top >= outer.top &&
220     inner.right <= outer.right && inner.bottom <= outer.bottom;
221}
222
223function screen_bounds(layer) {
224  if (layer.screenBounds) return layer.screenBounds;
225  const transformMatrix = layer.transform;
226  const tx = layer.position ? layer.position.x || 0 : 0;
227  const ty = layer.position ? layer.position.y || 0 : 0;
228
229  transformMatrix.tx = tx;
230  transformMatrix.ty = ty;
231  return multiply_rect(transformMatrix, layer.bounds);
232}
233
234// Traverse in z-order from top to bottom and fill in occlusion data
235function fill_occlusion_state(layerMap, rootLayers, includesCompositionState) {
236  const layers = rootLayers.filter((layer) => !layer.isRelativeOf);
237  traverse_top_to_bottom(layerMap, layers, {opaqueRects: [], transparentRects: [], screenBounds: null}, (layer, globalState) => {
238    if (layer.name.startsWith('Root#0') && layer.sourceBounds) {
239      globalState.screenBounds = {left: 0, top: 0, bottom: layer.sourceBounds.bottom, right: layer.sourceBounds.right};
240    }
241
242    const visible = is_visible(layer, layer.hidden, includesCompositionState);
243    if (visible) {
244      const fullyOccludes = (testLayer) => contains(testLayer, layer) && !layer.cornerRadius;
245      const partiallyOccludes = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer));
246      const covers = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer));
247
248      layer.occludedBy = globalState.opaqueRects.filter(fullyOccludes).map((layer) => layer.id);
249      layer.partiallyOccludedBy = globalState.opaqueRects.filter(partiallyOccludes)
250        .filter((p) => layer.occludedBy.indexOf(p.id) == -1)
251        .map((layer) => layer.id);
252      layer.coveredBy = globalState.transparentRects.filter(covers).map((layer) => layer.id);
253
254      if (is_opaque(layer)) {
255        globalState.opaqueRects.push(layer);
256      } else {
257        globalState.transparentRects.push(layer);
258      }
259    }
260
261    layer.visible = is_visible(layer, layer.hidden, includesCompositionState);
262    if (!layer.visible) {
263      layer.invisibleDueTo = get_visibility_reason(layer, includesCompositionState);
264    }
265  });
266}
267
268function traverse_top_to_bottom(layerMap, rootLayers, globalState, fn) {
269  for (let i = rootLayers.length-1; i >=0; i--) {
270    const relatives = [];
271    for (const id of rootLayers[i].relatives) {
272      if (!layerMap.hasOwnProperty(id)) {
273        // TODO (b/162500053): so that this doesn't need to be checked here
274        console.warn(
275            `Relative layer with id ${id} not found in dumped layers... ` +
276            `Skipping layer in traversal...`);
277      } else {
278        relatives.push(layerMap[id]);
279      }
280    }
281
282    const children = [];
283    for (const id of rootLayers[i].children) {
284      if (!layerMap.hasOwnProperty(id)) {
285        // TODO (b/162500053): so that this doesn't need to be checked here
286        console.warn(
287            `Child layer with id ${id} not found in dumped layers... ` +
288            `Skipping layer in traversal...`);
289      } else {
290        children.push(layerMap[id]);
291      }
292    }
293
294    // traverse through relatives and children that are not relatives
295    const traverseList = relatives
296        .concat(children.filter((layer) => !layer.isRelativeOf));
297
298    traverseList.sort((lhs, rhs) => rhs.z - lhs.z);
299
300    traverseList.filter((layer) => layer.z >=0).forEach((layer) => {
301      traverse_top_to_bottom(layerMap, [layer], globalState, fn);
302    });
303
304    fn(rootLayers[i], globalState);
305
306    traverseList.filter((layer) => layer.z < 0).forEach((layer) => {
307      traverse_top_to_bottom(layerMap, [layer], globalState, fn);
308    });
309  }
310}
311
312// Traverse all children and fill in any inherited states.
313function fill_inherited_state(layerMap, rootLayers) {
314  traverse(layerMap, rootLayers, (layer, parent) => {
315    const parentHidden = parent && parent.hidden;
316    layer.hidden = is_hidden_by_policy(layer) || parentHidden;
317    layer.verboseFlags = flags_to_string(layer.flags);
318
319    if (!layer.bounds) {
320      if (!layer.sourceBounds) {
321        layer.bounds = layer.sourceBounds;
322      } else if (parent) {
323        layer.bounds = parent.bounds;
324      } else {
325        layer.bounds = {left: 0, top: 0, right: 0, bottom: 0};
326      }
327    }
328  });
329}
330
331function traverse(layerMap, rootLayers, fn) {
332  for (let i = rootLayers.length-1; i >=0; i--) {
333    const parentId = rootLayers[i].parent;
334    const parent = parentId == -1 ? null : layerMap[parentId];
335    fn(rootLayers[i], parent);
336    const children = rootLayers[i].children.map(
337      (id) => {
338        const child = layerMap[id];
339        if (child == null) {
340          console.warn(
341            `Child layer with id ${id} in parent layer id ${rootLayers[i].id} not found... ` +
342            `Skipping layer in traversal...`);
343        }
344        return child;
345      }).filter(item => item !== undefined);
346    traverse(layerMap, children, fn);
347  }
348}
349
350export {fill_occlusion_state, fill_inherited_state};
351