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  var 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) { return is_empty_rect(r) } );
44}
45
46function is_empty_rect(rect) {
47  var right = rect.right || 0;
48  var left = rect.left || 0;
49  var top = rect.top || 0;
50  var bottom = rect.bottom || 0;
51
52  return (right - left) <= 0 || (bottom - top) <= 0;
53}
54
55function is_rect_empty_and_valid(rect) {
56  return rect &&
57    (rect.left - rect.right === 0 || rect.top - rect.bottom === 0);
58}
59
60/**
61 * The transformation matrix is defined as the product of:
62 * | cos(a) -sin(a) |  \/  | X 0 |
63 * | sin(a)  cos(a) |  /\  | 0 Y |
64 *
65 * where a is a rotation angle, and X and Y are scaling factors.
66 * A transformation matrix is invalid when either X or Y is zero,
67 * as a rotation matrix is valid for any angle. When either X or Y
68 * is 0, then the scaling matrix is not invertible, which makes the
69 * transformation matrix not invertible as well. A 2D matrix with
70 * components | A B | is not invertible if and only if AD - BC = 0.
71 *            | C D |
72 * This check is included above.
73 */
74function is_transform_invalid(transform) {
75  return !transform || (transform.dsdx * transform.dtdy ===
76      transform.dtdx * transform.dsdy); //determinant of transform
77}
78
79function is_opaque(layer) {
80  if (layer.color == undefined || layer.color.a == undefined || layer.color.a != 1) return false;
81  return layer.isOpaque;
82}
83
84function fills_color(layer) {
85  return layer.color && layer.color.a > 0 &&
86      layer.color.r >= 0 && layer.color.g >= 0 &&
87      layer.color.b >= 0;
88}
89
90function draws_shadows(layer) {
91  return layer.shadowRadius && layer.shadowRadius > 0;
92}
93
94function has_blur(layer) {
95  return layer.backgroundBlurRadius && layer.backgroundBlurRadius > 0;
96}
97
98function has_effects(layer) {
99  // Support previous color layer
100  if (layer.type === 'ColorLayer') return true;
101
102  // Support newer effect layer
103  return layer.type === 'EffectLayer' &&
104      (fills_color(layer) || draws_shadows(layer) || has_blur(layer))
105}
106
107function is_hidden_by_policy(layer) {
108  return layer.flags & FLAG_HIDDEN == FLAG_HIDDEN ||
109    // offscreen layer root has a unique layer id
110    layer.id == 0x7FFFFFFD;
111}
112
113/**
114 * Checks if the layer is visible based on its visibleRegion if available
115 * or its type, active buffer content, alpha and properties.
116 */
117function is_visible(layer, hiddenByPolicy, includesCompositionState) {
118
119  if (includesCompositionState) {
120    return !is_empty(layer.visibleRegion);
121  }
122
123  if (hiddenByPolicy) {
124    return false;
125  }
126
127  if (!layer.activeBuffer && !has_effects(layer)) {
128    return false;
129  }
130
131  if (!layer.color || !layer.color.a || layer.color.a == 0) {
132    return false;
133  }
134
135  if (layer.occludedBy && layer.occludedBy.length > 0) {
136    return false;
137  }
138
139  if (!layer.bounds || is_empty_rect(layer.bounds)) {
140    return false;
141  }
142
143  return true;
144}
145
146function get_visibility_reason(layer) {
147  if (layer.type === 'ContainerLayer') {
148    return 'ContainerLayer';
149  }
150
151  if (is_hidden_by_policy(layer)) {
152    return 'Flag is hidden';
153  }
154
155  if (layer.hidden) {
156    return 'Hidden by parent';
157  }
158
159  let isBufferLayer = (layer.type === 'BufferStateLayer' || layer.type === 'BufferQueueLayer');
160  if (isBufferLayer && (!layer.activeBuffer ||
161    layer.activeBuffer.height === 0 || layer.activeBuffer.width === 0)) {
162    return 'Buffer is empty';
163  }
164
165  if (!layer.color || !layer.color.a || layer.color.a == 0) {
166    return 'Alpha is 0';
167  }
168
169  if (is_rect_empty_and_valid(layer.crop)) {
170    return 'Crop is 0x0';
171  }
172
173  if (!layer.bounds || is_empty_rect(layer.bounds)) {
174    return 'Bounds is 0x0';
175  }
176
177  if (is_transform_invalid(layer.transform)) {
178    return 'Transform is invalid';
179  }
180  if (layer.isRelativeOf && layer.zOrderRelativeOf == -1) {
181    return 'RelativeOf layer has been removed';
182  }
183
184  let isEffectLayer = (layer.type === 'EffectLayer');
185  if (isEffectLayer && !fills_color(layer) && !draws_shadows(layer) && !has_blur(layer)) {
186    return 'Effect layer does not have color fill, shadow or blur';
187  }
188
189  if (layer.occludedBy && layer.occludedBy.length > 0) {
190    return 'Layer is occluded by:' + layer.occludedBy.join();
191  }
192
193  if (layer.visible) {
194    return "Unknown";
195  };
196}
197
198// Returns true if rectA overlaps rectB
199function overlaps(rectA, rectB) {
200  return rectA.left < rectB.right && rectA.right > rectB.left &&
201      rectA.top < rectB.bottom && rectA.bottom > rectA.top;
202}
203
204// Returns true if outer rect contains inner rect
205function contains(outerLayer, innerLayer) {
206  if (!is_simple_rotation(outerLayer.transform) || !is_simple_rotation(innerLayer.transform)) {
207    return false;
208  }
209  const outer = screen_bounds(outerLayer);
210  const inner = screen_bounds(innerLayer);
211  return inner.left >= outer.left && inner.top >= outer.top &&
212     inner.right <= outer.right && inner.bottom <= outer.bottom;
213}
214
215function screen_bounds(layer) {
216  if (layer.screenBounds) return layer.screenBounds;
217  let transformMatrix = layer.transform;
218  var tx = layer.position ? layer.position.x || 0 : 0;
219  var ty = layer.position ? layer.position.y || 0 : 0;
220
221  transformMatrix.tx = tx
222  transformMatrix.ty = ty
223  return multiply_rect(transformMatrix, layer.bounds);
224}
225
226// Traverse in z-order from top to bottom and fill in occlusion data
227function fill_occlusion_state(layerMap, rootLayers, includesCompositionState) {
228  const layers = rootLayers.filter(layer => !layer.isRelativeOf);
229  traverse_top_to_bottom(layerMap, layers, {opaqueRects:[], transparentRects:[], screenBounds:null}, (layer, globalState) => {
230
231    if (layer.name.startsWith("Root#0") && layer.sourceBounds) {
232      globalState.screenBounds = {left:0,  top:0, bottom:layer.sourceBounds.bottom, right:layer.sourceBounds.right};
233    }
234
235    const visible = is_visible(layer, layer.hidden, includesCompositionState);
236    if (visible) {
237      let fullyOccludes = (testLayer) => contains(testLayer, layer);
238      let partiallyOccludes = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer));
239      let covers = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer));
240
241      layer.occludedBy = globalState.opaqueRects.filter(fullyOccludes).map(layer => layer.id);
242      layer.partiallyOccludedBy = globalState.opaqueRects.filter(partiallyOccludes).map(layer => layer.id);
243      layer.coveredBy = globalState.transparentRects.filter(covers).map(layer => layer.id);
244
245      if (is_opaque(layer)) {
246        globalState.opaqueRects.push(layer);
247      } else {
248          globalState.transparentRects.push(layer);
249      }
250    }
251
252    layer.visible = is_visible(layer, layer.hidden, includesCompositionState);
253    if (!layer.visible) {
254      layer.invisibleDueTo = get_visibility_reason(layer);
255    }
256  });
257}
258
259function traverse_top_to_bottom(layerMap, rootLayers, globalState, fn) {
260  for (var i = rootLayers.length-1; i >=0; i--) {
261    const relatives = rootLayers[i].relatives.map(id => layerMap[id]);
262    const children = rootLayers[i].children.map(id => layerMap[id])
263
264    // traverse through relatives and children that are not relatives
265    const traverseList = relatives.concat(children.filter(layer => !layer.isRelativeOf));
266    traverseList.sort((lhs, rhs) => rhs.z - lhs.z);
267
268    traverseList.filter((layer) => layer.z >=0).forEach(layer => {
269      traverse_top_to_bottom(layerMap, [layer], globalState, fn);
270    });
271
272    fn(rootLayers[i], globalState);
273
274    traverseList.filter((layer) => layer.z < 0).forEach(layer => {
275      traverse_top_to_bottom(layerMap, [layer], globalState, fn);
276    });
277
278  }
279}
280
281// Traverse all children and fill in any inherited states.
282function fill_inherited_state(layerMap, rootLayers) {
283  traverse(layerMap, rootLayers, (layer, parent) => {
284    const parentHidden = parent && parent.hidden;
285    layer.hidden = is_hidden_by_policy(layer) || parentHidden;
286    layer.verboseFlags = flags_to_string(layer.flags);
287
288    if (!layer.bounds) {
289      if (!layer.sourceBounds) {
290        layer.bounds = layer.sourceBounds;
291      } else if (parent) {
292        layer.bounds = parent.bounds;
293      } else {
294        layer.bounds = {left:0, top:0, right:0, bottom:0};
295      }
296    }
297  });
298}
299
300function traverse(layerMap, rootLayers, fn) {
301  for (var i = rootLayers.length-1; i >=0; i--) {
302    const parentId = rootLayers[i].parent;
303    const parent = parentId == -1 ? null : layerMap[parentId];
304    fn(rootLayers[i], parent);
305    const children = rootLayers[i].children.map(id => layerMap[id]);
306    traverse(layerMap, children, fn);
307  }
308}
309
310export {fill_occlusion_state, fill_inherited_state};