1/*
2 * Copyright 2017, 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
17import {transform, nanos_to_string, get_visible_chip} from './transform.js'
18
19// Layer flags
20const FLAG_HIDDEN = 0x01;
21const FLAG_OPAQUE = 0x02;
22const FLAG_SECURE = 0x80;
23
24var RELATIVE_Z_CHIP = {short: 'RelZ',
25    long: "Is relative Z-ordered to another surface",
26    class: 'warn'};
27var RELATIVE_Z_PARENT_CHIP = {short: 'RelZParent',
28    long: "Something is relative Z-ordered to this surface",
29    class: 'warn'};
30var MISSING_LAYER = {short: 'MissingLayer',
31    long: "This layer was referenced from the parent, but not present in the trace",
32    class: 'error'};
33
34function transform_layer(layer, {parentBounds, parentHidden}) {
35  function get_size(layer) {
36    var size = layer.size || {w: 0, h: 0};
37    return {
38      left: 0,
39      right: size.w,
40      top: 0,
41      bottom: size.h
42    };
43  }
44
45  function get_crop(layer) {
46    var crop = layer.crop || {left: 0, top: 0, right: 0 , bottom:0};
47    return {
48      left: crop.left || 0,
49      right: crop.right  || 0,
50      top: crop.top || 0,
51      bottom: crop.bottom || 0
52    };
53  }
54
55  function intersect(bounds, crop) {
56    return {
57      left: Math.max(crop.left, bounds.left),
58      right: Math.min(crop.right, bounds.right),
59      top: Math.max(crop.top, bounds.top),
60      bottom: Math.min(crop.bottom, bounds.bottom),
61    };
62  }
63
64  function is_empty_rect(rect) {
65    var right = rect.right || 0;
66    var left = rect.left || 0;
67    var top = rect.top || 0;
68    var bottom = rect.bottom || 0;
69
70    return (right - left) <= 0 || (bottom - top) <= 0;
71  }
72
73  function get_cropped_bounds(layer, parentBounds) {
74    var size = get_size(layer);
75    var crop = get_crop(layer);
76    if (!is_empty_rect(size) && !is_empty_rect(crop)) {
77      return intersect(size, crop);
78    }
79    if (!is_empty_rect(size)) {
80      return size;
81    }
82    if (!is_empty_rect(crop)) {
83      return crop;
84    }
85    return parentBounds || { left: 0, right: 0, top: 0, bottom: 0 };
86  }
87
88  function offset_to(bounds, x, y) {
89    return {
90      right: bounds.right - (bounds.left - x),
91      bottom: bounds.bottom - (bounds.top - y),
92      left: x,
93      top: y,
94    };
95  }
96
97  function transform_bounds(layer, parentBounds) {
98    var result = layer.bounds || get_cropped_bounds(layer, parentBounds);
99    var tx = (layer.position) ? layer.position.x || 0 : 0;
100    var ty = (layer.position) ? layer.position.y || 0 : 0;
101    result = offset_to(result, 0, 0);
102    result.label = layer.name;
103    result.transform = layer.transform || {dsdx:1, dtdx:0, dsdy:0, dtdy:1};
104    result.transform.tx = tx;
105    result.transform.ty = ty;
106    return result;
107  }
108
109  function is_opaque(layer) {
110    return layer.color == undefined || (layer.color.a || 0) > 0;
111  }
112
113  function is_empty(region) {
114    return region == undefined ||
115        region.rect == undefined ||
116        region.rect.length == 0 ||
117        region.rect.every(function(r) { return is_empty_rect(r) } );
118  }
119
120  function is_rect_empty_and_valid(rect) {
121    return rect &&
122      (rect.left - rect.right === 0 || rect.top - rect.bottom === 0);
123  }
124
125  function is_transform_invalid(transform) {
126    return !transform || (transform.dsdx * transform.dtdy ===
127        transform.dtdx * transform.dsdy); //determinant of transform
128        /**
129         * The transformation matrix is defined as the product of:
130         * | cos(a) -sin(a) |  \/  | X 0 |
131         * | sin(a)  cos(a) |  /\  | 0 Y |
132         *
133         * where a is a rotation angle, and X and Y are scaling factors.
134         * A transformation matrix is invalid when either X or Y is zero,
135         * as a rotation matrix is valid for any angle. When either X or Y
136         * is 0, then the scaling matrix is not invertible, which makes the
137         * transformation matrix not invertible as well. A 2D matrix with
138         * components | A B | is uninvertible if and only if AD - BC = 0.
139         *            | C D |
140         * This check is included above.
141         */
142  }
143
144  /**
145   * Checks if the layer is visible on screen according to its type,
146   * active buffer content, alpha and visible regions.
147   *
148   * @param {layer} layer
149   * @returns if the layer is visible on screen or not
150   */
151  function is_visible(layer) {
152    var visible = (layer.activeBuffer || layer.type === 'ColorLayer')
153                  && !hidden && is_opaque(layer);
154    visible &= !is_empty(layer.visibleRegion);
155    return visible;
156  }
157
158  function postprocess_flags(layer) {
159    if (!layer.flags) return;
160    var verboseFlags = [];
161    if (layer.flags & FLAG_HIDDEN) {
162      verboseFlags.push("HIDDEN");
163    }
164    if (layer.flags & FLAG_OPAQUE) {
165      verboseFlags.push("OPAQUE");
166    }
167    if (layer.flags & FLAG_SECURE) {
168      verboseFlags.push("SECURE");
169    }
170
171    layer.flags = verboseFlags.join('|') + " (" + layer.flags + ")";
172  }
173
174  var chips = [];
175  var rect = transform_bounds(layer, parentBounds);
176  var hidden = (layer.flags & FLAG_HIDDEN) != 0 || parentHidden;
177  var visible = is_visible(layer);
178  if (visible) {
179    chips.push(get_visible_chip());
180  } else {
181    rect = undefined;
182  }
183
184  var bounds = undefined;
185  if (layer.name.startsWith("Display Root#0") && layer.sourceBounds) {
186    bounds = {width: layer.sourceBounds.right, height: layer.sourceBounds.bottom};
187  }
188
189  if ((layer.zOrderRelativeOf || -1) !== -1) {
190    chips.push(RELATIVE_Z_CHIP);
191  }
192  if (layer.zOrderRelativeParentOf !== undefined) {
193    chips.push(RELATIVE_Z_PARENT_CHIP);
194  }
195  if (layer.missing) {
196    chips.push(MISSING_LAYER);
197  }
198  function visibilityReason(layer) {
199    var reasons = [];
200    if (!layer.color || layer.color.a === 0) {
201      reasons.push('Alpha is 0');
202    }
203    if (layer.flags && (layer.flags & FLAG_HIDDEN != 0)) {
204      reasons.push('Flag is hidden');
205    }
206    if (is_rect_empty_and_valid(layer.crop)) {
207      reasons.push('Crop is zero');
208    }
209    if (is_transform_invalid(layer.transform)) {
210      reasons.push('Transform is invalid');
211    }
212    if (layer.isRelativeOf && layer.zOrderRelativeOf == -1) {
213      reasons.push('RelativeOf layer has been removed');
214    }
215    return reasons.join();
216  }
217  if (parentHidden) {
218    layer.invisibleDueTo = 'Hidden by parent with ID: ' + parentHidden;
219  } else {
220    let reasons_hidden = visibilityReason(layer);
221    let isBufferLayer = (layer.type === 'BufferStateLayer' || layer.type === 'BufferQueueLayer');
222    if (reasons_hidden) {
223      layer.invisibleDueTo = reasons_hidden;
224      parentHidden = layer.id
225    } else if (layer.type === 'ContainerLayer') {
226        layer.invisibleDueTo = 'This is a ContainerLayer.';
227    } else if (isBufferLayer && (!layer.activeBuffer ||
228          layer.activeBuffer.height === 0 || layer.activeBuffer.width === 0)) {
229        layer.invisibleDueTo = 'The buffer is empty.';
230    } else if (!visible) {
231        layer.invisibleDueTo = 'Unknown. Occluded by another layer?';
232    }
233  }
234  var transform_layer_with_parent_hidden =
235      (layer) => transform_layer(layer, {parentBounds: rect, parentHidden: parentHidden});
236  postprocess_flags(layer);
237  return transform({
238    obj: layer,
239    kind: '',
240    name: layer.id + ": " + layer.name,
241    children: [
242      [layer.resolvedChildren, transform_layer_with_parent_hidden],
243    ],
244    rect,
245    bounds,
246    highlight: rect,
247    chips,
248    visible,
249  });
250}
251
252function missingLayer(childId) {
253  return {
254    name: "layer #" + childId,
255    missing: true,
256    zOrderRelativeOf: -1,
257    transform: {dsdx:1, dtdx:0, dsdy:0, dtdy:1},
258  }
259}
260
261function transform_layers(layers) {
262  var idToItem = {};
263  var isChild = {}
264
265  var layersList = layers.layers || [];
266
267  layersList.forEach((e) => {
268    idToItem[e.id] = e;
269  });
270  layersList.forEach((e) => {
271    e.resolvedChildren = [];
272    if (Array.isArray(e.children)) {
273      e.resolvedChildren = e.children.map(
274          (childId) => idToItem[childId] || missingLayer(childId));
275      e.children.forEach((childId) => {
276        isChild[childId] = true;
277      });
278    }
279    if ((e.zOrderRelativeOf || -1) !== -1) {
280      idToItem[e.zOrderRelativeOf].zOrderRelativeParentOf = e.id;
281    }
282  });
283
284  var roots = layersList.filter((e) => !isChild[e.id]);
285
286  function foreachTree(nodes, fun) {
287    nodes.forEach((n) => {
288      fun(n);
289      foreachTree(n.children, fun);
290    });
291  }
292
293  var idToTransformed = {};
294  var transformed_roots = roots.map((r) =>
295    transform_layer(r, {parentBounds: {left: 0, right: 0, top: 0, bottom: 0},
296      parentHidden: null}));
297
298  foreachTree(transformed_roots, (n) => {
299    idToTransformed[n.obj.id] = n;
300  });
301  var flattened = [];
302  layersList.forEach((e) => {
303    flattened.push(idToTransformed[e.id]);
304  });
305
306  return transform({
307    obj: {},
308    kind: 'layers',
309    name: 'layers',
310    children: [
311      [transformed_roots, (c) => c],
312    ],
313    rects_transform (r) {
314      var res = [];
315      flattened.forEach((l) => {
316        if (l.rect) {
317          res.push(l.rect);
318        }
319      });
320      return res.reverse();
321    },
322    flattened,
323  });
324}
325
326function transform_layers_entry(entry) {
327  return transform({
328    obj: entry,
329    kind: 'entry',
330    name: nanos_to_string(entry.elapsedRealtimeNanos) + " - " + entry.where,
331    children: [
332      [[entry.layers], transform_layers],
333    ],
334    timestamp: entry.elapsedRealtimeNanos,
335    stableId: 'entry',
336  });
337}
338
339function transform_layers_trace(entries) {
340  var r = transform({
341    obj: entries,
342    kind: 'layerstrace',
343    name: 'layerstrace',
344    children: [
345      [entries.entry, transform_layers_entry],
346    ],
347  });
348
349  return r;
350}
351
352export {transform_layers, transform_layers_trace};
353