/* * 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'}; 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; 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) } ); } /** * 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 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); } var transform_layer_with_parent_hidden = (layer) => transform_layer(layer, {parentBounds: rect, parentHidden: hidden}); postprocess_flags(layer); return transform({ obj: layer, kind: 'layer', name: 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: false})); 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};