| /* |
| * Copyright 2017, The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import {transform, nanos_to_string, get_visible_chip} from './transform.js' |
| |
| // Layer flags |
| const FLAG_HIDDEN = 0x01; |
| const FLAG_OPAQUE = 0x02; |
| const FLAG_SECURE = 0x80; |
| |
| var RELATIVE_Z_CHIP = {short: 'RelZ', |
| long: "Is relative Z-ordered to another surface", |
| class: 'warn'}; |
| var RELATIVE_Z_PARENT_CHIP = {short: 'RelZParent', |
| long: "Something is relative Z-ordered to this surface", |
| class: 'warn'}; |
| var MISSING_LAYER = {short: 'MissingLayer', |
| long: "This layer was referenced from the parent, but not present in the trace", |
| class: 'error'}; |
| var GPU_CHIP = {short: 'GPU', |
| long: "This layer was composed on the GPU", |
| class: 'gpu'}; |
| var HWC_CHIP = {short: 'HWC', |
| long: "This layer was composed by Hardware Composer", |
| class: 'hwc'}; |
| |
| function transform_layer(layer, {parentBounds, parentHidden}) { |
| function get_size(layer) { |
| var size = layer.size || {w: 0, h: 0}; |
| return { |
| left: 0, |
| right: size.w, |
| top: 0, |
| bottom: size.h |
| }; |
| } |
| |
| function get_crop(layer) { |
| var crop = layer.crop || {left: 0, top: 0, right: 0 , bottom:0}; |
| return { |
| left: crop.left || 0, |
| right: crop.right || 0, |
| top: crop.top || 0, |
| bottom: crop.bottom || 0 |
| }; |
| } |
| |
| function intersect(bounds, crop) { |
| return { |
| left: Math.max(crop.left, bounds.left), |
| right: Math.min(crop.right, bounds.right), |
| top: Math.max(crop.top, bounds.top), |
| bottom: Math.min(crop.bottom, bounds.bottom), |
| }; |
| } |
| |
| function is_empty_rect(rect) { |
| var right = rect.right || 0; |
| var left = rect.left || 0; |
| var top = rect.top || 0; |
| var bottom = rect.bottom || 0; |
| |
| return (right - left) <= 0 || (bottom - top) <= 0; |
| } |
| |
| function get_cropped_bounds(layer, parentBounds) { |
| var size = get_size(layer); |
| var crop = get_crop(layer); |
| if (!is_empty_rect(size) && !is_empty_rect(crop)) { |
| return intersect(size, crop); |
| } |
| if (!is_empty_rect(size)) { |
| return size; |
| } |
| if (!is_empty_rect(crop)) { |
| return crop; |
| } |
| return parentBounds || { left: 0, right: 0, top: 0, bottom: 0 }; |
| } |
| |
| function offset_to(bounds, x, y) { |
| return { |
| right: bounds.right - (bounds.left - x), |
| bottom: bounds.bottom - (bounds.top - y), |
| left: x, |
| top: y, |
| }; |
| } |
| |
| function transform_bounds(layer, parentBounds) { |
| var result = layer.bounds || get_cropped_bounds(layer, parentBounds); |
| var tx = (layer.position) ? layer.position.x || 0 : 0; |
| var ty = (layer.position) ? layer.position.y || 0 : 0; |
| result = offset_to(result, 0, 0); |
| result.label = layer.name; |
| result.transform = layer.transform || {dsdx:1, dtdx:0, dsdy:0, dtdy:1}; |
| result.transform.tx = tx; |
| result.transform.ty = ty; |
| return result; |
| } |
| |
| function is_opaque(layer) { |
| return layer.color == undefined || (layer.color.a || 0) > 0; |
| } |
| |
| function is_empty(region) { |
| return region == undefined || |
| region.rect == undefined || |
| region.rect.length == 0 || |
| region.rect.every(function(r) { return is_empty_rect(r) } ); |
| } |
| |
| function is_rect_empty_and_valid(rect) { |
| return rect && |
| (rect.left - rect.right === 0 || rect.top - rect.bottom === 0); |
| } |
| |
| function is_transform_invalid(transform) { |
| return !transform || (transform.dsdx * transform.dtdy === |
| transform.dtdx * transform.dsdy); //determinant of transform |
| /** |
| * The transformation matrix is defined as the product of: |
| * | cos(a) -sin(a) | \/ | X 0 | |
| * | sin(a) cos(a) | /\ | 0 Y | |
| * |
| * where a is a rotation angle, and X and Y are scaling factors. |
| * A transformation matrix is invalid when either X or Y is zero, |
| * as a rotation matrix is valid for any angle. When either X or Y |
| * is 0, then the scaling matrix is not invertible, which makes the |
| * transformation matrix not invertible as well. A 2D matrix with |
| * components | A B | is uninvertible if and only if AD - BC = 0. |
| * | C D | |
| * This check is included above. |
| */ |
| } |
| |
| /** |
| * Checks if the layer is visible on screen according to its type, |
| * active buffer content, alpha and visible regions. |
| * |
| * @param {layer} layer |
| * @returns if the layer is visible on screen or not |
| */ |
| function is_visible(layer) { |
| var visible = (layer.activeBuffer || layer.type === 'ColorLayer') |
| && !hidden && is_opaque(layer); |
| visible &= !is_empty(layer.visibleRegion); |
| return visible; |
| } |
| |
| function add_hwc_composition_type_chip(layer) { |
| if (layer.hwcCompositionType === "CLIENT") { |
| chips.push(GPU_CHIP); |
| } else if (layer.hwcCompositionType === "DEVICE") { |
| chips.push(HWC_CHIP); |
| } |
| } |
| |
| function postprocess_flags(layer) { |
| if (!layer.flags) return; |
| var verboseFlags = []; |
| if (layer.flags & FLAG_HIDDEN) { |
| verboseFlags.push("HIDDEN"); |
| } |
| if (layer.flags & FLAG_OPAQUE) { |
| verboseFlags.push("OPAQUE"); |
| } |
| if (layer.flags & FLAG_SECURE) { |
| verboseFlags.push("SECURE"); |
| } |
| |
| layer.flags = verboseFlags.join('|') + " (" + layer.flags + ")"; |
| } |
| |
| var chips = []; |
| var rect = transform_bounds(layer, parentBounds); |
| var hidden = (layer.flags & FLAG_HIDDEN) != 0 || parentHidden; |
| var visible = is_visible(layer); |
| if (visible) { |
| chips.push(get_visible_chip()); |
| } else { |
| rect = undefined; |
| } |
| |
| var bounds = undefined; |
| if (layer.name.startsWith("Display Root#0") && layer.sourceBounds) { |
| bounds = {width: layer.sourceBounds.right, height: layer.sourceBounds.bottom}; |
| } |
| |
| if ((layer.zOrderRelativeOf || -1) !== -1) { |
| chips.push(RELATIVE_Z_CHIP); |
| } |
| if (layer.zOrderRelativeParentOf !== undefined) { |
| chips.push(RELATIVE_Z_PARENT_CHIP); |
| } |
| if (layer.missing) { |
| chips.push(MISSING_LAYER); |
| } |
| function visibilityReason(layer) { |
| var reasons = []; |
| if (!layer.color || layer.color.a === 0) { |
| reasons.push('Alpha is 0'); |
| } |
| if (layer.flags && (layer.flags & FLAG_HIDDEN != 0)) { |
| reasons.push('Flag is hidden'); |
| } |
| if (is_rect_empty_and_valid(layer.crop)) { |
| reasons.push('Crop is zero'); |
| } |
| if (is_transform_invalid(layer.transform)) { |
| reasons.push('Transform is invalid'); |
| } |
| if (layer.isRelativeOf && layer.zOrderRelativeOf == -1) { |
| reasons.push('RelativeOf layer has been removed'); |
| } |
| return reasons.join(); |
| } |
| if (parentHidden) { |
| layer.invisibleDueTo = 'Hidden by parent with ID: ' + parentHidden; |
| } else { |
| let reasons_hidden = visibilityReason(layer); |
| let isBufferLayer = (layer.type === 'BufferStateLayer' || layer.type === 'BufferQueueLayer'); |
| if (reasons_hidden) { |
| layer.invisibleDueTo = reasons_hidden; |
| parentHidden = layer.id |
| } else if (layer.type === 'ContainerLayer') { |
| layer.invisibleDueTo = 'This is a ContainerLayer.'; |
| } else if (isBufferLayer && (!layer.activeBuffer || |
| layer.activeBuffer.height === 0 || layer.activeBuffer.width === 0)) { |
| layer.invisibleDueTo = 'The buffer is empty.'; |
| } else if (!visible) { |
| layer.invisibleDueTo = 'Unknown. Occluded by another layer?'; |
| } |
| } |
| var transform_layer_with_parent_hidden = |
| (layer) => transform_layer(layer, {parentBounds: rect, parentHidden: parentHidden}); |
| postprocess_flags(layer); |
| add_hwc_composition_type_chip(layer); |
| return transform({ |
| obj: layer, |
| kind: '', |
| name: layer.id + ": " + layer.name, |
| children: [ |
| [layer.resolvedChildren, transform_layer_with_parent_hidden], |
| ], |
| rect, |
| bounds, |
| highlight: rect, |
| chips, |
| visible, |
| }); |
| } |
| |
| function missingLayer(childId) { |
| return { |
| name: "layer #" + childId, |
| missing: true, |
| zOrderRelativeOf: -1, |
| transform: {dsdx:1, dtdx:0, dsdy:0, dtdy:1}, |
| } |
| } |
| |
| function transform_layers(layers) { |
| var idToItem = {}; |
| var isChild = {} |
| |
| var layersList = layers.layers || []; |
| |
| layersList.forEach((e) => { |
| idToItem[e.id] = e; |
| }); |
| layersList.forEach((e) => { |
| e.resolvedChildren = []; |
| if (Array.isArray(e.children)) { |
| e.resolvedChildren = e.children.map( |
| (childId) => idToItem[childId] || missingLayer(childId)); |
| e.children.forEach((childId) => { |
| isChild[childId] = true; |
| }); |
| } |
| if ((e.zOrderRelativeOf || -1) !== -1) { |
| idToItem[e.zOrderRelativeOf].zOrderRelativeParentOf = e.id; |
| } |
| }); |
| |
| var roots = layersList.filter((e) => !isChild[e.id]); |
| |
| function foreachTree(nodes, fun) { |
| nodes.forEach((n) => { |
| fun(n); |
| foreachTree(n.children, fun); |
| }); |
| } |
| |
| var idToTransformed = {}; |
| var transformed_roots = roots.map((r) => |
| transform_layer(r, {parentBounds: {left: 0, right: 0, top: 0, bottom: 0}, |
| parentHidden: null})); |
| |
| foreachTree(transformed_roots, (n) => { |
| idToTransformed[n.obj.id] = n; |
| }); |
| var flattened = []; |
| layersList.forEach((e) => { |
| flattened.push(idToTransformed[e.id]); |
| }); |
| |
| return transform({ |
| obj: {}, |
| kind: 'layers', |
| name: 'layers', |
| children: [ |
| [transformed_roots, (c) => c], |
| ], |
| rects_transform (r) { |
| var res = []; |
| flattened.forEach((l) => { |
| if (l.rect) { |
| res.push(l.rect); |
| } |
| }); |
| return res.reverse(); |
| }, |
| flattened, |
| }); |
| } |
| |
| function transform_layers_entry(entry) { |
| return transform({ |
| obj: entry, |
| kind: 'entry', |
| name: nanos_to_string(entry.elapsedRealtimeNanos) + " - " + entry.where, |
| children: [ |
| [[entry.layers], transform_layers], |
| ], |
| timestamp: entry.elapsedRealtimeNanos, |
| stableId: 'entry', |
| }); |
| } |
| |
| function transform_layers_trace(entries) { |
| var r = transform({ |
| obj: entries, |
| kind: 'layerstrace', |
| name: 'layerstrace', |
| children: [ |
| [entries.entry, transform_layers_entry], |
| ], |
| }); |
| |
| return r; |
| } |
| |
| export {transform_layers, transform_layers_trace}; |