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 var 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) { return is_empty_rect(r) } ); 44} 45 46function is_empty_rect(rect) { 47 var right = rect.right || 0; 48 var left = rect.left || 0; 49 var top = rect.top || 0; 50 var bottom = rect.bottom || 0; 51 52 return (right - left) <= 0 || (bottom - top) <= 0; 53} 54 55function is_rect_empty_and_valid(rect) { 56 return rect && 57 (rect.left - rect.right === 0 || rect.top - rect.bottom === 0); 58} 59 60/** 61 * The transformation matrix is defined as the product of: 62 * | cos(a) -sin(a) | \/ | X 0 | 63 * | sin(a) cos(a) | /\ | 0 Y | 64 * 65 * where a is a rotation angle, and X and Y are scaling factors. 66 * A transformation matrix is invalid when either X or Y is zero, 67 * as a rotation matrix is valid for any angle. When either X or Y 68 * is 0, then the scaling matrix is not invertible, which makes the 69 * transformation matrix not invertible as well. A 2D matrix with 70 * components | A B | is not invertible if and only if AD - BC = 0. 71 * | C D | 72 * This check is included above. 73 */ 74function is_transform_invalid(transform) { 75 return !transform || (transform.dsdx * transform.dtdy === 76 transform.dtdx * transform.dsdy); //determinant of transform 77} 78 79function is_opaque(layer) { 80 if (layer.color == undefined || layer.color.a == undefined || layer.color.a != 1) return false; 81 return layer.isOpaque; 82} 83 84function fills_color(layer) { 85 return layer.color && layer.color.a > 0 && 86 layer.color.r >= 0 && layer.color.g >= 0 && 87 layer.color.b >= 0; 88} 89 90function draws_shadows(layer) { 91 return layer.shadowRadius && layer.shadowRadius > 0; 92} 93 94function has_blur(layer) { 95 return layer.backgroundBlurRadius && layer.backgroundBlurRadius > 0; 96} 97 98function has_effects(layer) { 99 // Support previous color layer 100 if (layer.type === 'ColorLayer') return true; 101 102 // Support newer effect layer 103 return layer.type === 'EffectLayer' && 104 (fills_color(layer) || draws_shadows(layer) || has_blur(layer)) 105} 106 107function is_hidden_by_policy(layer) { 108 return layer.flags & FLAG_HIDDEN == FLAG_HIDDEN || 109 // offscreen layer root has a unique layer id 110 layer.id == 0x7FFFFFFD; 111} 112 113/** 114 * Checks if the layer is visible based on its visibleRegion if available 115 * or its type, active buffer content, alpha and properties. 116 */ 117function is_visible(layer, hiddenByPolicy, includesCompositionState) { 118 119 if (includesCompositionState) { 120 return !is_empty(layer.visibleRegion); 121 } 122 123 if (hiddenByPolicy) { 124 return false; 125 } 126 127 if (!layer.activeBuffer && !has_effects(layer)) { 128 return false; 129 } 130 131 if (!layer.color || !layer.color.a || layer.color.a == 0) { 132 return false; 133 } 134 135 if (layer.occludedBy && layer.occludedBy.length > 0) { 136 return false; 137 } 138 139 if (!layer.bounds || is_empty_rect(layer.bounds)) { 140 return false; 141 } 142 143 return true; 144} 145 146function get_visibility_reason(layer) { 147 if (layer.type === 'ContainerLayer') { 148 return 'ContainerLayer'; 149 } 150 151 if (is_hidden_by_policy(layer)) { 152 return 'Flag is hidden'; 153 } 154 155 if (layer.hidden) { 156 return 'Hidden by parent'; 157 } 158 159 let isBufferLayer = (layer.type === 'BufferStateLayer' || layer.type === 'BufferQueueLayer'); 160 if (isBufferLayer && (!layer.activeBuffer || 161 layer.activeBuffer.height === 0 || layer.activeBuffer.width === 0)) { 162 return 'Buffer is empty'; 163 } 164 165 if (!layer.color || !layer.color.a || layer.color.a == 0) { 166 return 'Alpha is 0'; 167 } 168 169 if (is_rect_empty_and_valid(layer.crop)) { 170 return 'Crop is 0x0'; 171 } 172 173 if (!layer.bounds || is_empty_rect(layer.bounds)) { 174 return 'Bounds is 0x0'; 175 } 176 177 if (is_transform_invalid(layer.transform)) { 178 return 'Transform is invalid'; 179 } 180 if (layer.isRelativeOf && layer.zOrderRelativeOf == -1) { 181 return 'RelativeOf layer has been removed'; 182 } 183 184 let isEffectLayer = (layer.type === 'EffectLayer'); 185 if (isEffectLayer && !fills_color(layer) && !draws_shadows(layer) && !has_blur(layer)) { 186 return 'Effect layer does not have color fill, shadow or blur'; 187 } 188 189 if (layer.occludedBy && layer.occludedBy.length > 0) { 190 return 'Layer is occluded by:' + layer.occludedBy.join(); 191 } 192 193 if (layer.visible) { 194 return "Unknown"; 195 }; 196} 197 198// Returns true if rectA overlaps rectB 199function overlaps(rectA, rectB) { 200 return rectA.left < rectB.right && rectA.right > rectB.left && 201 rectA.top < rectB.bottom && rectA.bottom > rectA.top; 202} 203 204// Returns true if outer rect contains inner rect 205function contains(outerLayer, innerLayer) { 206 if (!is_simple_rotation(outerLayer.transform) || !is_simple_rotation(innerLayer.transform)) { 207 return false; 208 } 209 const outer = screen_bounds(outerLayer); 210 const inner = screen_bounds(innerLayer); 211 return inner.left >= outer.left && inner.top >= outer.top && 212 inner.right <= outer.right && inner.bottom <= outer.bottom; 213} 214 215function screen_bounds(layer) { 216 if (layer.screenBounds) return layer.screenBounds; 217 let transformMatrix = layer.transform; 218 var tx = layer.position ? layer.position.x || 0 : 0; 219 var ty = layer.position ? layer.position.y || 0 : 0; 220 221 transformMatrix.tx = tx 222 transformMatrix.ty = ty 223 return multiply_rect(transformMatrix, layer.bounds); 224} 225 226// Traverse in z-order from top to bottom and fill in occlusion data 227function fill_occlusion_state(layerMap, rootLayers, includesCompositionState) { 228 const layers = rootLayers.filter(layer => !layer.isRelativeOf); 229 traverse_top_to_bottom(layerMap, layers, {opaqueRects:[], transparentRects:[], screenBounds:null}, (layer, globalState) => { 230 231 if (layer.name.startsWith("Root#0") && layer.sourceBounds) { 232 globalState.screenBounds = {left:0, top:0, bottom:layer.sourceBounds.bottom, right:layer.sourceBounds.right}; 233 } 234 235 const visible = is_visible(layer, layer.hidden, includesCompositionState); 236 if (visible) { 237 let fullyOccludes = (testLayer) => contains(testLayer, layer); 238 let partiallyOccludes = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer)); 239 let covers = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer)); 240 241 layer.occludedBy = globalState.opaqueRects.filter(fullyOccludes).map(layer => layer.id); 242 layer.partiallyOccludedBy = globalState.opaqueRects.filter(partiallyOccludes).map(layer => layer.id); 243 layer.coveredBy = globalState.transparentRects.filter(covers).map(layer => layer.id); 244 245 if (is_opaque(layer)) { 246 globalState.opaqueRects.push(layer); 247 } else { 248 globalState.transparentRects.push(layer); 249 } 250 } 251 252 layer.visible = is_visible(layer, layer.hidden, includesCompositionState); 253 if (!layer.visible) { 254 layer.invisibleDueTo = get_visibility_reason(layer); 255 } 256 }); 257} 258 259function traverse_top_to_bottom(layerMap, rootLayers, globalState, fn) { 260 for (var i = rootLayers.length-1; i >=0; i--) { 261 const relatives = rootLayers[i].relatives.map(id => layerMap[id]); 262 const children = rootLayers[i].children.map(id => layerMap[id]) 263 264 // traverse through relatives and children that are not relatives 265 const traverseList = relatives.concat(children.filter(layer => !layer.isRelativeOf)); 266 traverseList.sort((lhs, rhs) => rhs.z - lhs.z); 267 268 traverseList.filter((layer) => layer.z >=0).forEach(layer => { 269 traverse_top_to_bottom(layerMap, [layer], globalState, fn); 270 }); 271 272 fn(rootLayers[i], globalState); 273 274 traverseList.filter((layer) => layer.z < 0).forEach(layer => { 275 traverse_top_to_bottom(layerMap, [layer], globalState, fn); 276 }); 277 278 } 279} 280 281// Traverse all children and fill in any inherited states. 282function fill_inherited_state(layerMap, rootLayers) { 283 traverse(layerMap, rootLayers, (layer, parent) => { 284 const parentHidden = parent && parent.hidden; 285 layer.hidden = is_hidden_by_policy(layer) || parentHidden; 286 layer.verboseFlags = flags_to_string(layer.flags); 287 288 if (!layer.bounds) { 289 if (!layer.sourceBounds) { 290 layer.bounds = layer.sourceBounds; 291 } else if (parent) { 292 layer.bounds = parent.bounds; 293 } else { 294 layer.bounds = {left:0, top:0, right:0, bottom:0}; 295 } 296 } 297 }); 298} 299 300function traverse(layerMap, rootLayers, fn) { 301 for (var i = rootLayers.length-1; i >=0; i--) { 302 const parentId = rootLayers[i].parent; 303 const parent = parentId == -1 ? null : layerMap[parentId]; 304 fn(rootLayers[i], parent); 305 const children = rootLayers[i].children.map(id => layerMap[id]); 306 traverse(layerMap, children, fn); 307 } 308} 309 310export {fill_occlusion_state, fill_inherited_state};