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 17 import {transform, nanos_to_string, get_visible_chip} from './transform.js' 18 19 // Layer flags 20 const FLAG_HIDDEN = 0x01; 21 const FLAG_OPAQUE = 0x02; 22 const FLAG_SECURE = 0x80; 23 24 var RELATIVE_Z_CHIP = {short: 'RelZ', 25 long: "Is relative Z-ordered to another surface", 26 class: 'warn'}; 27 var RELATIVE_Z_PARENT_CHIP = {short: 'RelZParent', 28 long: "Something is relative Z-ordered to this surface", 29 class: 'warn'}; 30 var MISSING_LAYER = {short: 'MissingLayer', 31 long: "This layer was referenced from the parent, but not present in the trace", 32 class: 'error'}; 33 34 function transform_layer(layer, {parentBounds, parentHidden}) { 35 36 function get_size(layer) { 37 var size = layer.size || {w: 0, h: 0}; 38 return { 39 left: 0, 40 right: size.w, 41 top: 0, 42 bottom: size.h 43 }; 44 } 45 46 function get_crop(layer) { 47 var crop = layer.crop || {left: 0, top: 0, right: 0 , bottom:0}; 48 return { 49 left: crop.left || 0, 50 right: crop.right || 0, 51 top: crop.top || 0, 52 bottom: crop.bottom || 0 53 }; 54 } 55 56 function intersect(bounds, crop) { 57 return { 58 left: Math.max(crop.left, bounds.left), 59 right: Math.min(crop.right, bounds.right), 60 top: Math.max(crop.top, bounds.top), 61 bottom: Math.min(crop.bottom, bounds.bottom), 62 }; 63 } 64 65 function is_empty_rect(rect) { 66 var right = rect.right || 0; 67 var left = rect.left || 0; 68 var top = rect.top || 0; 69 var bottom = rect.bottom || 0; 70 71 return (right - left) <= 0 || (bottom - top) <= 0; 72 } 73 74 function get_cropped_bounds(layer, parentBounds) { 75 var size = get_size(layer); 76 var crop = get_crop(layer); 77 if (!is_empty_rect(size) && !is_empty_rect(crop)) { 78 return intersect(size, crop); 79 } 80 if (!is_empty_rect(size)) { 81 return size; 82 } 83 if (!is_empty_rect(crop)) { 84 return crop; 85 } 86 return parentBounds || { left: 0, right: 0, top: 0, bottom: 0 }; 87 } 88 89 function offset_to(bounds, x, y) { 90 return { 91 right: bounds.right - (bounds.left - x), 92 bottom: bounds.bottom - (bounds.top - y), 93 left: x, 94 top: y, 95 }; 96 } 97 98 function transform_bounds(layer, parentBounds) { 99 var result = layer.bounds || get_cropped_bounds(layer, parentBounds); 100 var tx = (layer.position) ? layer.position.x || 0 : 0; 101 var ty = (layer.position) ? layer.position.y || 0 : 0; 102 result = offset_to(result, 0, 0); 103 result.label = layer.name; 104 result.transform = layer.transform; 105 result.transform.tx = tx; 106 result.transform.ty = ty; 107 return result; 108 } 109 110 function is_opaque(layer) { 111 return layer.color == undefined || (layer.color.a || 0) > 0; 112 } 113 114 function is_empty(region) { 115 return region == undefined || 116 region.rect == undefined || 117 region.rect.length == 0 || 118 region.rect.every(function(r) { return is_empty_rect(r) } ); 119 } 120 121 /** 122 * Checks if the layer is visible on screen according to its type, 123 * active buffer content, alpha and visible regions. 124 * 125 * @param {layer} layer 126 * @returns if the layer is visible on screen or not 127 */ 128 function is_visible(layer) { 129 var visible = (layer.activeBuffer || layer.type === 'ColorLayer') 130 && !hidden && is_opaque(layer); 131 visible &= !is_empty(layer.visibleRegion); 132 return visible; 133 } 134 135 function postprocess_flags(layer) { 136 if (!layer.flags) return; 137 var verboseFlags = []; 138 if (layer.flags & FLAG_HIDDEN) { 139 verboseFlags.push("HIDDEN"); 140 } 141 if (layer.flags & FLAG_OPAQUE) { 142 verboseFlags.push("OPAQUE"); 143 } 144 if (layer.flags & FLAG_SECURE) { 145 verboseFlags.push("SECURE"); 146 } 147 148 layer.flags = verboseFlags.join('|') + " (" + layer.flags + ")"; 149 } 150 151 var chips = []; 152 var rect = transform_bounds(layer, parentBounds); 153 var hidden = (layer.flags & FLAG_HIDDEN) != 0 || parentHidden; 154 var visible = is_visible(layer); 155 if (visible) { 156 chips.push(get_visible_chip()); 157 } else { 158 rect = undefined; 159 } 160 161 var bounds = undefined; 162 if (layer.name.startsWith("Display Root#0") && layer.sourceBounds) { 163 bounds = {width: layer.sourceBounds.right, height: layer.sourceBounds.bottom}; 164 } 165 166 if ((layer.zOrderRelativeOf || -1) !== -1) { 167 chips.push(RELATIVE_Z_CHIP); 168 } 169 if (layer.zOrderRelativeParentOf !== undefined) { 170 chips.push(RELATIVE_Z_PARENT_CHIP); 171 } 172 if (layer.missing) { 173 chips.push(MISSING_LAYER); 174 } 175 176 var transform_layer_with_parent_hidden = 177 (layer) => transform_layer(layer, {parentBounds: rect, parentHidden: hidden}); 178 179 postprocess_flags(layer); 180 181 return transform({ 182 obj: layer, 183 kind: 'layer', 184 name: layer.name, 185 children: [ 186 [layer.resolvedChildren, transform_layer_with_parent_hidden], 187 ], 188 rect, 189 bounds, 190 highlight: rect, 191 chips, 192 visible, 193 }); 194 } 195 196 function missingLayer(childId) { 197 return { 198 name: "layer #" + childId, 199 missing: true, 200 zOrderRelativeOf: -1, 201 transform: {dsdx:1, dtdx:0, dsdy:0, dtdy:1}, 202 } 203 } 204 205 function transform_layers(layers) { 206 var idToItem = {}; 207 var isChild = {} 208 209 var layersList = layers.layers || []; 210 211 layersList.forEach((e) => { 212 idToItem[e.id] = e; 213 }); 214 layersList.forEach((e) => { 215 e.resolvedChildren = []; 216 if (Array.isArray(e.children)) { 217 e.resolvedChildren = e.children.map( 218 (childId) => idToItem[childId] || missingLayer(childId)); 219 e.children.forEach((childId) => { 220 isChild[childId] = true; 221 }); 222 } 223 if ((e.zOrderRelativeOf || -1) !== -1) { 224 idToItem[e.zOrderRelativeOf].zOrderRelativeParentOf = e.id; 225 } 226 }); 227 228 var roots = layersList.filter((e) => !isChild[e.id]); 229 230 function foreachTree(nodes, fun) { 231 nodes.forEach((n) => { 232 fun(n); 233 foreachTree(n.children, fun); 234 }); 235 } 236 237 var idToTransformed = {}; 238 var transformed_roots = roots.map((r) => 239 transform_layer(r, {parentBounds: {left: 0, right: 0, top: 0, bottom: 0}, 240 parentHidden: false})); 241 242 foreachTree(transformed_roots, (n) => { 243 idToTransformed[n.obj.id] = n; 244 }); 245 var flattened = []; 246 layersList.forEach((e) => { 247 flattened.push(idToTransformed[e.id]); 248 }); 249 250 return transform({ 251 obj: {}, 252 kind: 'layers', 253 name: 'layers', 254 children: [ 255 [transformed_roots, (c) => c], 256 ], 257 rects_transform (r) { 258 var res = []; 259 flattened.forEach((l) => { 260 if (l.rect) { 261 res.push(l.rect); 262 } 263 }); 264 return res.reverse(); 265 }, 266 flattened, 267 }); 268 } 269 270 function transform_layers_entry(entry) { 271 return transform({ 272 obj: entry, 273 kind: 'entry', 274 name: nanos_to_string(entry.elapsedRealtimeNanos) + " - " + entry.where, 275 children: [ 276 [[entry.layers], transform_layers], 277 ], 278 timestamp: entry.elapsedRealtimeNanos, 279 stableId: 'entry', 280 }); 281 } 282 283 function transform_layers_trace(entries) { 284 var r = transform({ 285 obj: entries, 286 kind: 'layerstrace', 287 name: 'layerstrace', 288 children: [ 289 [entries.entry, transform_layers_entry], 290 ], 291 }); 292 293 return r; 294 } 295 296 export {transform_layers, transform_layers_trace}; 297