blob: 8d6c20d217c05250857d007126692ee6e7693288 [file] [log] [blame]
/*
* 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.
*/
// eslint-disable-next-line camelcase
import {transform, nanos_to_string, get_visible_chip} from './transform.js';
// eslint-disable-next-line camelcase
import {fill_occlusion_state, fill_inherited_state} from './sf_visibility.js';
import {getSimplifiedLayerName} from './utils/names';
const RELATIVE_Z_CHIP = {
short: 'RelZ',
long: 'Is relative Z-ordered to another surface',
class: 'warn',
};
const RELATIVE_Z_PARENT_CHIP = {
short: 'RelZParent',
long: 'Something is relative Z-ordered to this surface',
class: 'warn',
};
const MISSING_LAYER = {
short: 'MissingLayer',
long:
'This layer was referenced from the parent, but not present in the trace',
class: 'error',
};
const GPU_CHIP = {
short: 'GPU',
long: 'This layer was composed on the GPU',
class: 'gpu',
};
const HWC_CHIP = {
short: 'HWC',
long: 'This layer was composed by Hardware Composer',
class: 'hwc',
};
function transformLayer(layer) {
function offsetTo(bounds, x, y) {
return {
right: bounds.right - (bounds.left - x),
bottom: bounds.bottom - (bounds.top - y),
left: x,
top: y,
};
}
function getRect(layer) {
let result = layer.bounds;
const tx = layer.position ? layer.position.x || 0 : 0;
const ty = layer.position ? layer.position.y || 0 : 0;
result.label = layer.name;
result.transform = layer.transform;
result.transform.tx = tx;
result.transform.ty = ty;
return result;
}
function addHwcCompositionTypeChip(layer) {
if (layer.hwcCompositionType === 'CLIENT') {
chips.push(GPU_CHIP);
} else if (layer.hwcCompositionType === 'DEVICE' ||
layer.hwcCompositionType === 'SOLID_COLOR') {
chips.push(HWC_CHIP);
}
}
const chips = [];
if (layer.visible) {
chips.push(get_visible_chip());
}
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);
}
addHwcCompositionTypeChip(layer);
const rect = layer.visible && layer.bounds !== null ?
getRect(layer) : undefined;
const simplifiedLayerName = getSimplifiedLayerName(layer.name);
const shortName = simplifiedLayerName ?
layer.id + ': ' + simplifiedLayerName : undefined;
const transformedLayer = transform({
obj: layer,
kind: '',
name: layer.id + ': ' + layer.name,
shortName,
children: [[layer.resolvedChildren, transformLayer]],
rect,
undefined /* bounds */,
highlight: rect,
chips,
visible: layer.visible,
freeze: false,
});
// NOTE: Temporary until refactored to use flickerlib
transformedLayer.invisibleDueTo = layer.invisibleDueTo;
transformedLayer.occludedBy = layer.occludedBy;
transformedLayer.partiallyOccludedBy = layer.partiallyOccludedBy;
transformedLayer.coveredBy = layer.coveredBy;
return Object.freeze(transformedLayer);
}
function missingLayer(childId) {
return {
name: 'layer #' + childId,
missing: true,
zOrderRelativeOf: -1,
transform: {dsdx: 1, dtdx: 0, dsdy: 0, dtdy: 1},
};
}
function transformLayers(includesCompositionState, layers) {
const idToItem = {};
const isChild = {};
const 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;
});
}
// We don't clean up relatives when the relative parent is removed, so it
// may be inconsistent
if ((e.zOrderRelativeOf || -1) !== -1 && (idToItem[e.zOrderRelativeOf])) {
idToItem[e.zOrderRelativeOf].zOrderRelativeParentOf = e.id;
}
});
const roots = layersList.filter((e) => !isChild[e.id]);
fill_inherited_state(idToItem, roots);
// Backwards compatibility check
const occlusionDetectionCompatible = roots[0].bounds !== null;
if (occlusionDetectionCompatible) {
fill_occlusion_state(idToItem, roots, includesCompositionState);
}
function foreachTree(nodes, fun) {
nodes.forEach((n) => {
fun(n);
foreachTree(n.children, fun);
});
}
const idToTransformed = {};
const transformedRoots = roots.map((r) =>
transformLayer(r, {
parentBounds: {left: 0, right: 0, top: 0, bottom: 0},
parentHidden: null,
}));
foreachTree(transformedRoots, (n) => {
idToTransformed[n.obj.id] = n;
});
const flattened = [];
layersList.forEach((e) => {
flattened.push(idToTransformed[e.id]);
});
return transform({
obj: {},
kind: 'layers',
name: 'layers',
children: [
[transformedRoots, (c) => c],
],
rectsTransform(r) {
const res = [];
flattened.forEach((l) => {
if (l.rect) {
res.push(l.rect);
}
});
return res.reverse();
},
flattened,
});
}
function transformLayersEntry(entry) {
const includesCompositionState = !entry.excludesCompositionState;
return transform({
obj: entry,
kind: 'entry',
name: nanos_to_string(entry.elapsedRealtimeNanos) + ' - ' + entry.where,
children: [
[
[entry.layers],
(layer) => transformLayers(includesCompositionState, layer),
],
],
timestamp: entry.elapsedRealtimeNanos,
stableId: 'entry',
});
}
function transformLayersTrace(entries) {
const r = transform({
obj: entries,
kind: 'layerstrace',
name: 'layerstrace',
children: [
[entries.entry, transformLayersEntry],
],
});
return r;
}
export {transformLayers, transformLayersTrace};