/* * Copyright 2020, 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. */ /** * Utility class for deriving state and visibility from the hierarchy. This * duplicates some of the logic in surface flinger. If the trace contains * composition state (visibleRegion), it will be used otherwise it will be * derived. */ import {multiply_rect, is_simple_rotation} from './matrix_utils.js'; // Layer flags const FLAG_HIDDEN = 0x01; const FLAG_OPAQUE = 0x02; const FLAG_SECURE = 0x80; function flags_to_string(flags) { if (!flags) return ''; const verboseFlags = []; if (flags & FLAG_HIDDEN) verboseFlags.push('HIDDEN'); if (flags & FLAG_OPAQUE) verboseFlags.push('OPAQUE'); if (flags & FLAG_SECURE) verboseFlags.push('SECURE'); return verboseFlags.join('|') + ' (' + flags + ')'; } 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_empty_rect(rect) { const right = rect.right || 0; const left = rect.left || 0; const top = rect.top || 0; const bottom = rect.bottom || 0; return (right - left) <= 0 || (bottom - top) <= 0; } function is_rect_empty_and_valid(rect) { return rect && (rect.left - rect.right === 0 || rect.top - rect.bottom === 0); } /** * 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 not invertible if and only if AD - BC = 0. * | C D | * This check is included above. */ function is_transform_invalid(transform) { return !transform || (transform.dsdx * transform.dtdy === transform.dtdx * transform.dsdy); // determinant of transform } function is_opaque(layer) { if (layer.color == undefined || layer.color.a == undefined || layer.color.a != 1) return false; return layer.isOpaque; } function fills_color(layer) { return layer.color && layer.color.a > 0 && layer.color.r >= 0 && layer.color.g >= 0 && layer.color.b >= 0; } function draws_shadows(layer) { return layer.shadowRadius && layer.shadowRadius > 0; } function has_blur(layer) { return layer.backgroundBlurRadius && layer.backgroundBlurRadius > 0; } function has_effects(layer) { // Support previous color layer if (layer.type === 'ColorLayer') return true; // Support newer effect layer return layer.type === 'EffectLayer' && (fills_color(layer) || draws_shadows(layer) || has_blur(layer)); } function is_hidden_by_policy(layer) { return layer.flags & FLAG_HIDDEN == FLAG_HIDDEN || // offscreen layer root has a unique layer id layer.id == 0x7FFFFFFD; } /** * Checks if the layer is visible based on its visibleRegion if available * or its type, active buffer content, alpha and properties. */ function is_visible(layer, hiddenByPolicy, includesCompositionState) { if (includesCompositionState) { return !is_empty(layer.visibleRegion); } if (hiddenByPolicy) { return false; } if (!layer.activeBuffer && !has_effects(layer)) { return false; } if (!layer.color || !layer.color.a || layer.color.a == 0) { return false; } if (layer.occludedBy && layer.occludedBy.length > 0) { return false; } if (!layer.bounds || is_empty_rect(layer.bounds)) { return false; } return true; } function get_visibility_reason(layer, includesCompositionState) { if (layer.type === 'ContainerLayer') { return 'ContainerLayer'; } if (is_hidden_by_policy(layer)) { return 'Flag is hidden'; } if (layer.hidden) { return 'Hidden by parent'; } const isBufferLayer = (layer.type === 'BufferStateLayer' || layer.type === 'BufferQueueLayer'); if (isBufferLayer && (!layer.activeBuffer || layer.activeBuffer.height === 0 || layer.activeBuffer.width === 0)) { return 'Buffer is empty'; } if (!layer.color || !layer.color.a || layer.color.a == 0) { return 'Alpha is 0'; } if (is_rect_empty_and_valid(layer.crop)) { return 'Crop is 0x0'; } if (!layer.bounds || is_empty_rect(layer.bounds)) { return 'Bounds is 0x0'; } if (is_transform_invalid(layer.transform)) { return 'Transform is invalid'; } if (layer.isRelativeOf && layer.zOrderRelativeOf == -1) { return 'RelativeOf layer has been removed'; } const isEffectLayer = (layer.type === 'EffectLayer'); if (isEffectLayer && !fills_color(layer) && !draws_shadows(layer) && !has_blur(layer)) { return 'Effect layer does not have color fill, shadow or blur'; } if (layer.occludedBy && layer.occludedBy.length > 0) { return 'Layer is occluded by:' + layer.occludedBy.join(); } if (includesCompositionState && is_empty(layer.visibleRegion)) { return 'Visible region calculated by Composition Engine is empty'; } if (layer.visible) { return 'Unknown'; }; } // Returns true if rectA overlaps rectB function overlaps(rectA, rectB) { return rectA.left < rectB.right && rectA.right > rectB.left && rectA.top < rectB.bottom && rectA.bottom > rectA.top; } // Returns true if outer rect contains inner rect function contains(outerLayer, innerLayer) { if (!is_simple_rotation(outerLayer.transform) || !is_simple_rotation(innerLayer.transform)) { return false; } const outer = screen_bounds(outerLayer); const inner = screen_bounds(innerLayer); return inner.left >= outer.left && inner.top >= outer.top && inner.right <= outer.right && inner.bottom <= outer.bottom; } function screen_bounds(layer) { if (layer.screenBounds) return layer.screenBounds; const transformMatrix = layer.transform; const tx = layer.position ? layer.position.x || 0 : 0; const ty = layer.position ? layer.position.y || 0 : 0; transformMatrix.tx = tx; transformMatrix.ty = ty; return multiply_rect(transformMatrix, layer.bounds); } // Traverse in z-order from top to bottom and fill in occlusion data function fill_occlusion_state(layerMap, rootLayers, includesCompositionState) { const layers = rootLayers.filter((layer) => !layer.isRelativeOf); traverse_top_to_bottom(layerMap, layers, {opaqueRects: [], transparentRects: [], screenBounds: null}, (layer, globalState) => { if (layer.name.startsWith('Root#0') && layer.sourceBounds) { globalState.screenBounds = {left: 0, top: 0, bottom: layer.sourceBounds.bottom, right: layer.sourceBounds.right}; } const visible = is_visible(layer, layer.hidden, includesCompositionState); if (visible) { const fullyOccludes = (testLayer) => contains(testLayer, layer) && !layer.cornerRadius; const partiallyOccludes = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer)); const covers = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer)); layer.occludedBy = globalState.opaqueRects.filter(fullyOccludes).map((layer) => layer.id); layer.partiallyOccludedBy = globalState.opaqueRects.filter(partiallyOccludes) .filter((p) => layer.occludedBy.indexOf(p.id) == -1) .map((layer) => layer.id); layer.coveredBy = globalState.transparentRects.filter(covers).map((layer) => layer.id); if (is_opaque(layer)) { globalState.opaqueRects.push(layer); } else { globalState.transparentRects.push(layer); } } layer.visible = is_visible(layer, layer.hidden, includesCompositionState); if (!layer.visible) { layer.invisibleDueTo = get_visibility_reason(layer, includesCompositionState); } }); } function traverse_top_to_bottom(layerMap, rootLayers, globalState, fn) { for (let i = rootLayers.length-1; i >=0; i--) { const relatives = []; for (const id of rootLayers[i].relatives) { if (!layerMap.hasOwnProperty(id)) { // TODO (b/162500053): so that this doesn't need to be checked here console.warn( `Relative layer with id ${id} not found in dumped layers... ` + `Skipping layer in traversal...`); } else { relatives.push(layerMap[id]); } } const children = []; for (const id of rootLayers[i].children) { if (!layerMap.hasOwnProperty(id)) { // TODO (b/162500053): so that this doesn't need to be checked here console.warn( `Child layer with id ${id} not found in dumped layers... ` + `Skipping layer in traversal...`); } else { children.push(layerMap[id]); } } // traverse through relatives and children that are not relatives const traverseList = relatives .concat(children.filter((layer) => !layer.isRelativeOf)); traverseList.sort((lhs, rhs) => rhs.z - lhs.z); traverseList.filter((layer) => layer.z >=0).forEach((layer) => { traverse_top_to_bottom(layerMap, [layer], globalState, fn); }); fn(rootLayers[i], globalState); traverseList.filter((layer) => layer.z < 0).forEach((layer) => { traverse_top_to_bottom(layerMap, [layer], globalState, fn); }); } } // Traverse all children and fill in any inherited states. function fill_inherited_state(layerMap, rootLayers) { traverse(layerMap, rootLayers, (layer, parent) => { const parentHidden = parent && parent.hidden; layer.hidden = is_hidden_by_policy(layer) || parentHidden; layer.verboseFlags = flags_to_string(layer.flags); if (!layer.bounds) { if (!layer.sourceBounds) { layer.bounds = layer.sourceBounds; } else if (parent) { layer.bounds = parent.bounds; } else { layer.bounds = {left: 0, top: 0, right: 0, bottom: 0}; } } }); } function traverse(layerMap, rootLayers, fn) { for (let i = rootLayers.length-1; i >=0; i--) { const parentId = rootLayers[i].parent; const parent = parentId == -1 ? null : layerMap[parentId]; fn(rootLayers[i], parent); const children = rootLayers[i].children.map( (id) => { const child = layerMap[id]; if (child == null) { console.warn( `Child layer with id ${id} in parent layer id ${rootLayers[i].id} not found... ` + `Skipping layer in traversal...`); } return child; }).filter(item => item !== undefined); traverse(layerMap, children, fn); } } export {fill_occlusion_state, fill_inherited_state};