/*
* 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};