1/* 2 * Copyright (C) 2024 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 {assertDefined} from 'common/assert_utils'; 18import {Rect} from 'common/rect'; 19import {Transform} from 'parsers/surface_flinger/transform_utils'; 20import {TraceRect} from 'trace/trace_rect'; 21import {TraceRectBuilder} from 'trace/trace_rect_builder'; 22import {Computation} from 'trace/tree_node/computation'; 23import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; 24import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 25 26class RectSfFactory { 27 makeDisplayRects(displays: readonly PropertyTreeNode[]): TraceRect[] { 28 const nameCounts = new Map<string, number>(); 29 return displays.map((display, index) => { 30 const layerStackSpaceRect = assertDefined( 31 display.getChildByName('layerStackSpaceRect'), 32 ); 33 const displayRect = Rect.from(layerStackSpaceRect); 34 const layerStack = assertDefined( 35 display.getChildByName('layerStack'), 36 ).getValue(); 37 let displayName = display.getChildByName('name')?.getValue(); 38 if (!displayName) { 39 displayName = 'Unknown Display'; 40 } 41 const id = assertDefined(display.getChildByName('id')).getValue(); 42 43 const existingNameCount = nameCounts.get(displayName); 44 if (existingNameCount !== undefined) { 45 nameCounts.set(displayName, existingNameCount + 1); 46 const qualifier = displayName === 'Unknown Display' ? '' : 'Mirror '; 47 displayName += ` (${qualifier}${existingNameCount + 1})`; 48 } else { 49 nameCounts.set(displayName, 1); 50 } 51 52 return new TraceRectBuilder() 53 .setX(displayRect.x) 54 .setY(displayRect.y) 55 .setWidth(displayRect.w) 56 .setHeight(displayRect.h) 57 .setId(`Display - ${id}`) 58 .setName(displayName) 59 .setCornerRadius(0) 60 .setTransform(Transform.EMPTY.matrix) 61 .setGroupId(layerStack) 62 .setIsVisible(false) 63 .setIsDisplay(true) 64 .setIsVirtual(display.getChildByName('isVirtual')?.getValue() ?? false) 65 .setDepth(index) 66 .build(); 67 }); 68 } 69 70 makeLayerRect( 71 layer: HierarchyTreeNode, 72 layerStack: number, 73 absoluteZ: number, 74 ): TraceRect { 75 const isVisible = assertDefined( 76 layer.getEagerPropertyByName('isComputedVisible'), 77 ).getValue(); 78 79 const name = assertDefined(layer.getEagerPropertyByName('name')).getValue(); 80 const bounds = assertDefined(layer.getEagerPropertyByName('bounds')); 81 const boundsRect = Rect.from(bounds); 82 83 let opacity = layer 84 .getEagerPropertyByName('color') 85 ?.getChildByName('a') 86 ?.getValue(); 87 if (isVisible && opacity === undefined) opacity = 0; 88 89 return new TraceRectBuilder() 90 .setX(boundsRect.x) 91 .setY(boundsRect.y) 92 .setWidth(boundsRect.w) 93 .setHeight(boundsRect.h) 94 .setId( 95 `${assertDefined( 96 layer.getEagerPropertyByName('id'), 97 ).getValue()} ${name}`, 98 ) 99 .setName(name) 100 .setCornerRadius( 101 layer.getEagerPropertyByName('cornerRadius')?.getValue() ?? 0, 102 ) 103 .setTransform( 104 Transform.from(assertDefined(layer.getEagerPropertyByName('transform'))) 105 .matrix, 106 ) 107 .setGroupId(layerStack) 108 .setIsVisible(isVisible) 109 .setIsDisplay(false) 110 .setIsVirtual(false) 111 .setDepth(absoluteZ) 112 .setOpacity(opacity) 113 .build(); 114 } 115} 116 117export class RectsComputation implements Computation { 118 private root: HierarchyTreeNode | undefined; 119 private readonly rectsFactory = new RectSfFactory(); 120 121 setRoot(value: HierarchyTreeNode): this { 122 this.root = value; 123 return this; 124 } 125 126 executeInPlace(): void { 127 if (!this.root) { 128 throw Error('root not set'); 129 } 130 const groupIdToAbsoluteZ = new Map<number, number>(); 131 132 const displays = 133 this.root.getEagerPropertyByName('displays')?.getAllChildren() ?? []; 134 const displayRects = this.rectsFactory.makeDisplayRects(displays); 135 this.root.setRects(displayRects); 136 137 displayRects.forEach((displayRect) => 138 groupIdToAbsoluteZ.set(displayRect.groupId, 1), 139 ); 140 141 const layersWithRects = this.extractLayersWithRects(this.root); 142 layersWithRects.sort(this.compareLayerZ); 143 144 for (let i = layersWithRects.length - 1; i > -1; i--) { 145 const layer = layersWithRects[i]; 146 const layerStack = assertDefined( 147 layer.getEagerPropertyByName('layerStack'), 148 ).getValue(); 149 const absoluteZ = groupIdToAbsoluteZ.get(layerStack) ?? 0; 150 const rect = this.rectsFactory.makeLayerRect( 151 layer, 152 layerStack, 153 absoluteZ, 154 ); 155 layer.setRects([rect]); 156 groupIdToAbsoluteZ.set(layerStack, absoluteZ + 1); 157 } 158 } 159 160 private extractLayersWithRects( 161 hierarchyRoot: HierarchyTreeNode, 162 ): HierarchyTreeNode[] { 163 return hierarchyRoot.filterDfs(this.hasLayerRect); 164 } 165 166 private hasLayerRect(node: HierarchyTreeNode): boolean { 167 if (node.isRoot()) return false; 168 169 const isVisible = node 170 .getEagerPropertyByName('isComputedVisible') 171 ?.getValue(); 172 if (isVisible === undefined) { 173 throw Error('Visibility has not been computed'); 174 } 175 176 const occludedBy = node.getEagerPropertyByName('occludedBy'); 177 if ( 178 !isVisible && 179 (occludedBy === undefined || occludedBy.getAllChildren().length === 0) 180 ) { 181 return false; 182 } 183 184 const bounds = node.getEagerPropertyByName('bounds'); 185 if (!bounds) return false; 186 187 return true; 188 } 189 190 private compareLayerZ(a: HierarchyTreeNode, b: HierarchyTreeNode): number { 191 const aZOrderPath: number[] = assertDefined( 192 a.getEagerPropertyByName('zOrderPath'), 193 ) 194 .getAllChildren() 195 .map((child) => child.getValue()); 196 const bZOrderPath: number[] = assertDefined( 197 b.getEagerPropertyByName('zOrderPath'), 198 ) 199 .getAllChildren() 200 .map((child) => child.getValue()); 201 202 const zipLength = Math.min(aZOrderPath.length, bZOrderPath.length); 203 for (let i = 0; i < zipLength; ++i) { 204 const zOrderA = aZOrderPath[i]; 205 const zOrderB = bZOrderPath[i]; 206 if (zOrderA > zOrderB) return -1; 207 if (zOrderA < zOrderB) return 1; 208 } 209 // When z-order is the same, the layer with larger ID is on top 210 return a.id > b.id ? -1 : 1; 211 } 212} 213