1/* 2 * Copyright 2020, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/** 18 * Utility class for deriving state and visibility from the hierarchy. This 19 * duplicates some of the logic in surface flinger. If the trace contains 20 * composition state (visibleRegion), it will be used otherwise it will be 21 * derived. 22 */ 23import {multiply_rect, is_simple_rotation} from './matrix_utils.js'; 24 25// Layer flags 26const FLAG_HIDDEN = 0x01; 27const FLAG_OPAQUE = 0x02; 28const FLAG_SECURE = 0x80; 29 30function flags_to_string(flags) { 31 if (!flags) return ''; 32 const verboseFlags = []; 33 if (flags & FLAG_HIDDEN) verboseFlags.push('HIDDEN'); 34 if (flags & FLAG_OPAQUE) verboseFlags.push('OPAQUE'); 35 if (flags & FLAG_SECURE) verboseFlags.push('SECURE'); 36 return verboseFlags.join('|') + ' (' + flags + ')'; 37} 38 39function is_empty(region) { 40 return region == undefined || 41 region.rect == undefined || 42 region.rect.length == 0 || 43 region.rect.every(function(r) { 44 return is_empty_rect(r); 45 } ); 46} 47 48function is_empty_rect(rect) { 49 const right = rect.right || 0; 50 const left = rect.left || 0; 51 const top = rect.top || 0; 52 const bottom = rect.bottom || 0; 53 54 return (right - left) <= 0 || (bottom - top) <= 0; 55} 56 57function is_rect_empty_and_valid(rect) { 58 return rect && 59 (rect.left - rect.right === 0 || rect.top - rect.bottom === 0); 60} 61 62/** 63 * The transformation matrix is defined as the product of: 64 * | cos(a) -sin(a) | \/ | X 0 | 65 * | sin(a) cos(a) | /\ | 0 Y | 66 * 67 * where a is a rotation angle, and X and Y are scaling factors. 68 * A transformation matrix is invalid when either X or Y is zero, 69 * as a rotation matrix is valid for any angle. When either X or Y 70 * is 0, then the scaling matrix is not invertible, which makes the 71 * transformation matrix not invertible as well. A 2D matrix with 72 * components | A B | is not invertible if and only if AD - BC = 0. 73 * | C D | 74 * This check is included above. 75 */ 76function is_transform_invalid(transform) { 77 return !transform || (transform.dsdx * transform.dtdy === 78 transform.dtdx * transform.dsdy); // determinant of transform 79} 80 81function is_opaque(layer) { 82 if (layer.color == undefined || layer.color.a == undefined || layer.color.a != 1) return false; 83 return layer.isOpaque; 84} 85 86function fills_color(layer) { 87 return layer.color && layer.color.a > 0 && 88 layer.color.r >= 0 && layer.color.g >= 0 && 89 layer.color.b >= 0; 90} 91 92function draws_shadows(layer) { 93 return layer.shadowRadius && layer.shadowRadius > 0; 94} 95 96function has_blur(layer) { 97 return layer.backgroundBlurRadius && layer.backgroundBlurRadius > 0; 98} 99 100function has_effects(layer) { 101 // Support previous color layer 102 if (layer.type === 'ColorLayer') return true; 103 104 // Support newer effect layer 105 return layer.type === 'EffectLayer' && 106 (fills_color(layer) || draws_shadows(layer) || has_blur(layer)); 107} 108 109function is_hidden_by_policy(layer) { 110 return layer.flags & FLAG_HIDDEN == FLAG_HIDDEN || 111 // offscreen layer root has a unique layer id 112 layer.id == 0x7FFFFFFD; 113} 114 115/** 116 * Checks if the layer is visible based on its visibleRegion if available 117 * or its type, active buffer content, alpha and properties. 118 */ 119function is_visible(layer, hiddenByPolicy, includesCompositionState) { 120 if (includesCompositionState) { 121 return !is_empty(layer.visibleRegion); 122 } 123 124 if (hiddenByPolicy) { 125 return false; 126 } 127 128 if (!layer.activeBuffer && !has_effects(layer)) { 129 return false; 130 } 131 132 if (!layer.color || !layer.color.a || layer.color.a == 0) { 133 return false; 134 } 135 136 if (layer.occludedBy && layer.occludedBy.length > 0) { 137 return false; 138 } 139 140 if (!layer.bounds || is_empty_rect(layer.bounds)) { 141 return false; 142 } 143 144 return true; 145} 146 147function get_visibility_reason(layer, includesCompositionState) { 148 if (layer.type === 'ContainerLayer') { 149 return 'ContainerLayer'; 150 } 151 152 if (is_hidden_by_policy(layer)) { 153 return 'Flag is hidden'; 154 } 155 156 if (layer.hidden) { 157 return 'Hidden by parent'; 158 } 159 160 const isBufferLayer = (layer.type === 'BufferStateLayer' || 161 layer.type === 'BufferQueueLayer'); 162 if (isBufferLayer && (!layer.activeBuffer || 163 layer.activeBuffer.height === 0 || layer.activeBuffer.width === 0)) { 164 return 'Buffer is empty'; 165 } 166 167 if (!layer.color || !layer.color.a || layer.color.a == 0) { 168 return 'Alpha is 0'; 169 } 170 171 if (is_rect_empty_and_valid(layer.crop)) { 172 return 'Crop is 0x0'; 173 } 174 175 if (!layer.bounds || is_empty_rect(layer.bounds)) { 176 return 'Bounds is 0x0'; 177 } 178 179 if (is_transform_invalid(layer.transform)) { 180 return 'Transform is invalid'; 181 } 182 if (layer.isRelativeOf && layer.zOrderRelativeOf == -1) { 183 return 'RelativeOf layer has been removed'; 184 } 185 186 const isEffectLayer = (layer.type === 'EffectLayer'); 187 if (isEffectLayer && !fills_color(layer) && 188 !draws_shadows(layer) && !has_blur(layer)) { 189 return 'Effect layer does not have color fill, shadow or blur'; 190 } 191 192 if (layer.occludedBy && layer.occludedBy.length > 0) { 193 return 'Layer is occluded by:' + layer.occludedBy.join(); 194 } 195 196 if (includesCompositionState && is_empty(layer.visibleRegion)) { 197 return 'Visible region calculated by Composition Engine is empty'; 198 } 199 200 if (layer.visible) { 201 return 'Unknown'; 202 }; 203} 204 205// Returns true if rectA overlaps rectB 206function overlaps(rectA, rectB) { 207 return rectA.left < rectB.right && rectA.right > rectB.left && 208 rectA.top < rectB.bottom && rectA.bottom > rectA.top; 209} 210 211// Returns true if outer rect contains inner rect 212function contains(outerLayer, innerLayer) { 213 if (!is_simple_rotation(outerLayer.transform) || 214 !is_simple_rotation(innerLayer.transform)) { 215 return false; 216 } 217 const outer = screen_bounds(outerLayer); 218 const inner = screen_bounds(innerLayer); 219 return inner.left >= outer.left && inner.top >= outer.top && 220 inner.right <= outer.right && inner.bottom <= outer.bottom; 221} 222 223function screen_bounds(layer) { 224 if (layer.screenBounds) return layer.screenBounds; 225 const transformMatrix = layer.transform; 226 const tx = layer.position ? layer.position.x || 0 : 0; 227 const ty = layer.position ? layer.position.y || 0 : 0; 228 229 transformMatrix.tx = tx; 230 transformMatrix.ty = ty; 231 return multiply_rect(transformMatrix, layer.bounds); 232} 233 234// Traverse in z-order from top to bottom and fill in occlusion data 235function fill_occlusion_state(layerMap, rootLayers, includesCompositionState) { 236 const layers = rootLayers.filter((layer) => !layer.isRelativeOf); 237 traverse_top_to_bottom(layerMap, layers, {opaqueRects: [], transparentRects: [], screenBounds: null}, (layer, globalState) => { 238 if (layer.name.startsWith('Root#0') && layer.sourceBounds) { 239 globalState.screenBounds = {left: 0, top: 0, bottom: layer.sourceBounds.bottom, right: layer.sourceBounds.right}; 240 } 241 242 const visible = is_visible(layer, layer.hidden, includesCompositionState); 243 if (visible) { 244 const fullyOccludes = (testLayer) => contains(testLayer, layer) && !layer.cornerRadius; 245 const partiallyOccludes = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer)); 246 const covers = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer)); 247 248 layer.occludedBy = globalState.opaqueRects.filter(fullyOccludes).map((layer) => layer.id); 249 layer.partiallyOccludedBy = globalState.opaqueRects.filter(partiallyOccludes) 250 .filter((p) => layer.occludedBy.indexOf(p.id) == -1) 251 .map((layer) => layer.id); 252 layer.coveredBy = globalState.transparentRects.filter(covers).map((layer) => layer.id); 253 254 if (is_opaque(layer)) { 255 globalState.opaqueRects.push(layer); 256 } else { 257 globalState.transparentRects.push(layer); 258 } 259 } 260 261 layer.visible = is_visible(layer, layer.hidden, includesCompositionState); 262 if (!layer.visible) { 263 layer.invisibleDueTo = get_visibility_reason(layer, includesCompositionState); 264 } 265 }); 266} 267 268function traverse_top_to_bottom(layerMap, rootLayers, globalState, fn) { 269 for (let i = rootLayers.length-1; i >=0; i--) { 270 const relatives = []; 271 for (const id of rootLayers[i].relatives) { 272 if (!layerMap.hasOwnProperty(id)) { 273 // TODO (b/162500053): so that this doesn't need to be checked here 274 console.warn( 275 `Relative layer with id ${id} not found in dumped layers... ` + 276 `Skipping layer in traversal...`); 277 } else { 278 relatives.push(layerMap[id]); 279 } 280 } 281 282 const children = []; 283 for (const id of rootLayers[i].children) { 284 if (!layerMap.hasOwnProperty(id)) { 285 // TODO (b/162500053): so that this doesn't need to be checked here 286 console.warn( 287 `Child layer with id ${id} not found in dumped layers... ` + 288 `Skipping layer in traversal...`); 289 } else { 290 children.push(layerMap[id]); 291 } 292 } 293 294 // traverse through relatives and children that are not relatives 295 const traverseList = relatives 296 .concat(children.filter((layer) => !layer.isRelativeOf)); 297 298 traverseList.sort((lhs, rhs) => rhs.z - lhs.z); 299 300 traverseList.filter((layer) => layer.z >=0).forEach((layer) => { 301 traverse_top_to_bottom(layerMap, [layer], globalState, fn); 302 }); 303 304 fn(rootLayers[i], globalState); 305 306 traverseList.filter((layer) => layer.z < 0).forEach((layer) => { 307 traverse_top_to_bottom(layerMap, [layer], globalState, fn); 308 }); 309 } 310} 311 312// Traverse all children and fill in any inherited states. 313function fill_inherited_state(layerMap, rootLayers) { 314 traverse(layerMap, rootLayers, (layer, parent) => { 315 const parentHidden = parent && parent.hidden; 316 layer.hidden = is_hidden_by_policy(layer) || parentHidden; 317 layer.verboseFlags = flags_to_string(layer.flags); 318 319 if (!layer.bounds) { 320 if (!layer.sourceBounds) { 321 layer.bounds = layer.sourceBounds; 322 } else if (parent) { 323 layer.bounds = parent.bounds; 324 } else { 325 layer.bounds = {left: 0, top: 0, right: 0, bottom: 0}; 326 } 327 } 328 }); 329} 330 331function traverse(layerMap, rootLayers, fn) { 332 for (let i = rootLayers.length-1; i >=0; i--) { 333 const parentId = rootLayers[i].parent; 334 const parent = parentId == -1 ? null : layerMap[parentId]; 335 fn(rootLayers[i], parent); 336 const children = rootLayers[i].children.map( 337 (id) => { 338 const child = layerMap[id]; 339 if (child == null) { 340 console.warn( 341 `Child layer with id ${id} in parent layer id ${rootLayers[i].id} not found... ` + 342 `Skipping layer in traversal...`); 343 } 344 return child; 345 }).filter(item => item !== undefined); 346 traverse(layerMap, children, fn); 347 } 348} 349 350export {fill_occlusion_state, fill_inherited_state}; 351