• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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