1/* 2 * Copyright 2017, 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 17import {transform, nanos_to_string, get_visible_chip} from './transform.js' 18 19// Layer flags 20const FLAG_HIDDEN = 0x01; 21const FLAG_OPAQUE = 0x02; 22const FLAG_SECURE = 0x80; 23 24var RELATIVE_Z_CHIP = {short: 'RelZ', 25 long: "Is relative Z-ordered to another surface", 26 class: 'warn'}; 27var RELATIVE_Z_PARENT_CHIP = {short: 'RelZParent', 28 long: "Something is relative Z-ordered to this surface", 29 class: 'warn'}; 30var MISSING_LAYER = {short: 'MissingLayer', 31 long: "This layer was referenced from the parent, but not present in the trace", 32 class: 'error'}; 33 34function transform_layer(layer, {parentBounds, parentHidden}) { 35 function get_size(layer) { 36 var size = layer.size || {w: 0, h: 0}; 37 return { 38 left: 0, 39 right: size.w, 40 top: 0, 41 bottom: size.h 42 }; 43 } 44 45 function get_crop(layer) { 46 var crop = layer.crop || {left: 0, top: 0, right: 0 , bottom:0}; 47 return { 48 left: crop.left || 0, 49 right: crop.right || 0, 50 top: crop.top || 0, 51 bottom: crop.bottom || 0 52 }; 53 } 54 55 function intersect(bounds, crop) { 56 return { 57 left: Math.max(crop.left, bounds.left), 58 right: Math.min(crop.right, bounds.right), 59 top: Math.max(crop.top, bounds.top), 60 bottom: Math.min(crop.bottom, bounds.bottom), 61 }; 62 } 63 64 function is_empty_rect(rect) { 65 var right = rect.right || 0; 66 var left = rect.left || 0; 67 var top = rect.top || 0; 68 var bottom = rect.bottom || 0; 69 70 return (right - left) <= 0 || (bottom - top) <= 0; 71 } 72 73 function get_cropped_bounds(layer, parentBounds) { 74 var size = get_size(layer); 75 var crop = get_crop(layer); 76 if (!is_empty_rect(size) && !is_empty_rect(crop)) { 77 return intersect(size, crop); 78 } 79 if (!is_empty_rect(size)) { 80 return size; 81 } 82 if (!is_empty_rect(crop)) { 83 return crop; 84 } 85 return parentBounds || { left: 0, right: 0, top: 0, bottom: 0 }; 86 } 87 88 function offset_to(bounds, x, y) { 89 return { 90 right: bounds.right - (bounds.left - x), 91 bottom: bounds.bottom - (bounds.top - y), 92 left: x, 93 top: y, 94 }; 95 } 96 97 function transform_bounds(layer, parentBounds) { 98 var result = layer.bounds || get_cropped_bounds(layer, parentBounds); 99 var tx = (layer.position) ? layer.position.x || 0 : 0; 100 var ty = (layer.position) ? layer.position.y || 0 : 0; 101 result = offset_to(result, 0, 0); 102 result.label = layer.name; 103 result.transform = layer.transform || {dsdx:1, dtdx:0, dsdy:0, dtdy:1}; 104 result.transform.tx = tx; 105 result.transform.ty = ty; 106 return result; 107 } 108 109 function is_opaque(layer) { 110 return layer.color == undefined || (layer.color.a || 0) > 0; 111 } 112 113 function is_empty(region) { 114 return region == undefined || 115 region.rect == undefined || 116 region.rect.length == 0 || 117 region.rect.every(function(r) { return is_empty_rect(r) } ); 118 } 119 120 function is_rect_empty_and_valid(rect) { 121 return rect && 122 (rect.left - rect.right === 0 || rect.top - rect.bottom === 0); 123 } 124 125 function is_transform_invalid(transform) { 126 return !transform || (transform.dsdx * transform.dtdy === 127 transform.dtdx * transform.dsdy); //determinant of transform 128 /** 129 * The transformation matrix is defined as the product of: 130 * | cos(a) -sin(a) | \/ | X 0 | 131 * | sin(a) cos(a) | /\ | 0 Y | 132 * 133 * where a is a rotation angle, and X and Y are scaling factors. 134 * A transformation matrix is invalid when either X or Y is zero, 135 * as a rotation matrix is valid for any angle. When either X or Y 136 * is 0, then the scaling matrix is not invertible, which makes the 137 * transformation matrix not invertible as well. A 2D matrix with 138 * components | A B | is uninvertible if and only if AD - BC = 0. 139 * | C D | 140 * This check is included above. 141 */ 142 } 143 144 /** 145 * Checks if the layer is visible on screen according to its type, 146 * active buffer content, alpha and visible regions. 147 * 148 * @param {layer} layer 149 * @returns if the layer is visible on screen or not 150 */ 151 function is_visible(layer) { 152 var visible = (layer.activeBuffer || layer.type === 'ColorLayer') 153 && !hidden && is_opaque(layer); 154 visible &= !is_empty(layer.visibleRegion); 155 return visible; 156 } 157 158 function postprocess_flags(layer) { 159 if (!layer.flags) return; 160 var verboseFlags = []; 161 if (layer.flags & FLAG_HIDDEN) { 162 verboseFlags.push("HIDDEN"); 163 } 164 if (layer.flags & FLAG_OPAQUE) { 165 verboseFlags.push("OPAQUE"); 166 } 167 if (layer.flags & FLAG_SECURE) { 168 verboseFlags.push("SECURE"); 169 } 170 171 layer.flags = verboseFlags.join('|') + " (" + layer.flags + ")"; 172 } 173 174 var chips = []; 175 var rect = transform_bounds(layer, parentBounds); 176 var hidden = (layer.flags & FLAG_HIDDEN) != 0 || parentHidden; 177 var visible = is_visible(layer); 178 if (visible) { 179 chips.push(get_visible_chip()); 180 } else { 181 rect = undefined; 182 } 183 184 var bounds = undefined; 185 if (layer.name.startsWith("Display Root#0") && layer.sourceBounds) { 186 bounds = {width: layer.sourceBounds.right, height: layer.sourceBounds.bottom}; 187 } 188 189 if ((layer.zOrderRelativeOf || -1) !== -1) { 190 chips.push(RELATIVE_Z_CHIP); 191 } 192 if (layer.zOrderRelativeParentOf !== undefined) { 193 chips.push(RELATIVE_Z_PARENT_CHIP); 194 } 195 if (layer.missing) { 196 chips.push(MISSING_LAYER); 197 } 198 function visibilityReason(layer) { 199 var reasons = []; 200 if (!layer.color || layer.color.a === 0) { 201 reasons.push('Alpha is 0'); 202 } 203 if (layer.flags && (layer.flags & FLAG_HIDDEN != 0)) { 204 reasons.push('Flag is hidden'); 205 } 206 if (is_rect_empty_and_valid(layer.crop)) { 207 reasons.push('Crop is zero'); 208 } 209 if (is_transform_invalid(layer.transform)) { 210 reasons.push('Transform is invalid'); 211 } 212 if (layer.isRelativeOf && layer.zOrderRelativeOf == -1) { 213 reasons.push('RelativeOf layer has been removed'); 214 } 215 return reasons.join(); 216 } 217 if (parentHidden) { 218 layer.invisibleDueTo = 'Hidden by parent with ID: ' + parentHidden; 219 } else { 220 let reasons_hidden = visibilityReason(layer); 221 let isBufferLayer = (layer.type === 'BufferStateLayer' || layer.type === 'BufferQueueLayer'); 222 if (reasons_hidden) { 223 layer.invisibleDueTo = reasons_hidden; 224 parentHidden = layer.id 225 } else if (layer.type === 'ContainerLayer') { 226 layer.invisibleDueTo = 'This is a ContainerLayer.'; 227 } else if (isBufferLayer && (!layer.activeBuffer || 228 layer.activeBuffer.height === 0 || layer.activeBuffer.width === 0)) { 229 layer.invisibleDueTo = 'The buffer is empty.'; 230 } else if (!visible) { 231 layer.invisibleDueTo = 'Unknown. Occluded by another layer?'; 232 } 233 } 234 var transform_layer_with_parent_hidden = 235 (layer) => transform_layer(layer, {parentBounds: rect, parentHidden: parentHidden}); 236 postprocess_flags(layer); 237 return transform({ 238 obj: layer, 239 kind: '', 240 name: layer.id + ": " + layer.name, 241 children: [ 242 [layer.resolvedChildren, transform_layer_with_parent_hidden], 243 ], 244 rect, 245 bounds, 246 highlight: rect, 247 chips, 248 visible, 249 }); 250} 251 252function missingLayer(childId) { 253 return { 254 name: "layer #" + childId, 255 missing: true, 256 zOrderRelativeOf: -1, 257 transform: {dsdx:1, dtdx:0, dsdy:0, dtdy:1}, 258 } 259} 260 261function transform_layers(layers) { 262 var idToItem = {}; 263 var isChild = {} 264 265 var layersList = layers.layers || []; 266 267 layersList.forEach((e) => { 268 idToItem[e.id] = e; 269 }); 270 layersList.forEach((e) => { 271 e.resolvedChildren = []; 272 if (Array.isArray(e.children)) { 273 e.resolvedChildren = e.children.map( 274 (childId) => idToItem[childId] || missingLayer(childId)); 275 e.children.forEach((childId) => { 276 isChild[childId] = true; 277 }); 278 } 279 if ((e.zOrderRelativeOf || -1) !== -1) { 280 idToItem[e.zOrderRelativeOf].zOrderRelativeParentOf = e.id; 281 } 282 }); 283 284 var roots = layersList.filter((e) => !isChild[e.id]); 285 286 function foreachTree(nodes, fun) { 287 nodes.forEach((n) => { 288 fun(n); 289 foreachTree(n.children, fun); 290 }); 291 } 292 293 var idToTransformed = {}; 294 var transformed_roots = roots.map((r) => 295 transform_layer(r, {parentBounds: {left: 0, right: 0, top: 0, bottom: 0}, 296 parentHidden: null})); 297 298 foreachTree(transformed_roots, (n) => { 299 idToTransformed[n.obj.id] = n; 300 }); 301 var flattened = []; 302 layersList.forEach((e) => { 303 flattened.push(idToTransformed[e.id]); 304 }); 305 306 return transform({ 307 obj: {}, 308 kind: 'layers', 309 name: 'layers', 310 children: [ 311 [transformed_roots, (c) => c], 312 ], 313 rects_transform (r) { 314 var res = []; 315 flattened.forEach((l) => { 316 if (l.rect) { 317 res.push(l.rect); 318 } 319 }); 320 return res.reverse(); 321 }, 322 flattened, 323 }); 324} 325 326function transform_layers_entry(entry) { 327 return transform({ 328 obj: entry, 329 kind: 'entry', 330 name: nanos_to_string(entry.elapsedRealtimeNanos) + " - " + entry.where, 331 children: [ 332 [[entry.layers], transform_layers], 333 ], 334 timestamp: entry.elapsedRealtimeNanos, 335 stableId: 'entry', 336 }); 337} 338 339function transform_layers_trace(entries) { 340 var r = transform({ 341 obj: entries, 342 kind: 'layerstrace', 343 name: 'layerstrace', 344 children: [ 345 [entries.entry, transform_layers_entry], 346 ], 347 }); 348 349 return r; 350} 351 352export {transform_layers, transform_layers_trace}; 353