• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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 package android.tools.common.traces.surfaceflinger
18 
19 import android.tools.common.Timestamp
20 import kotlin.js.JsExport
21 import kotlin.js.JsName
22 
23 /** Builder for LayerTraceEntries */
24 @JsExport
25 class LayerTraceEntryBuilder {
26     private var elapsedTimestamp: Long = 0L
27     private var realTimestamp: Long? = null
28     private var orphanLayerCallback: ((Layer) -> Boolean)? = null
29     private val orphans = mutableListOf<Layer>()
30     private var layers: MutableMap<Int, Layer> = mutableMapOf()
31     private var ignoreVirtualDisplay = false
32     private var ignoreLayersStackMatchNoDisplay = false
33     private var timestamp: Timestamp? = null
34     private var displays: Array<Display> = emptyArray()
35     private var vSyncId: Long = 0L
36     private var hwcBlob: String = ""
37     private var where: String = ""
38     private var duplicateLayerCallback: ((Layer) -> Boolean) = {
39         error("Duplicate layer id found: ${it.id}")
40     }
41 
42     @JsName("setVSyncId")
43     fun setVSyncId(vSyncId: String): LayerTraceEntryBuilder =
44         // Necessary for compatibility with JS number type
45         apply { this.vSyncId = vSyncId.toLong() }
46 
47     @JsName("setHwcBlob")
48     fun setHwcBlob(hwcBlob: String): LayerTraceEntryBuilder = apply { this.hwcBlob = hwcBlob }
49 
50     @JsName("setWhere")
51     fun setWhere(where: String): LayerTraceEntryBuilder = apply { this.where = where }
52 
53     @JsName("setDisplays")
54     fun setDisplays(displays: Array<Display>): LayerTraceEntryBuilder = apply {
55         this.displays = displays
56     }
57 
58     @JsName("setElapsedTimestamp")
59     fun setElapsedTimestamp(timestamp: String): LayerTraceEntryBuilder =
60         // Necessary for compatibility with JS number type
61         apply { this.elapsedTimestamp = timestamp.toLong() }
62 
63     @JsName("setRealToElapsedTimeOffsetNs")
64     fun setRealToElapsedTimeOffsetNs(realToElapsedTimeOffsetNs: String?): LayerTraceEntryBuilder =
65         apply {
66             this.realTimestamp =
67                 if (realToElapsedTimeOffsetNs != null && realToElapsedTimeOffsetNs.toLong() != 0L) {
68                     realToElapsedTimeOffsetNs.toLong() + elapsedTimestamp
69                 } else {
70                     null
71                 }
72         }
73 
74     @JsName("setLayers")
75     fun setLayers(layers: Array<Layer>): LayerTraceEntryBuilder = apply {
76         val result = mutableMapOf<Int, Layer>()
77         layers.forEach { layer ->
78             val id = layer.id
79             if (result.containsKey(id)) {
80                 duplicateLayerCallback(layer)
81             }
82             result[id] = layer
83         }
84 
85         this.layers = result
86     }
87 
88     @JsName("setOrphanLayerCallback")
89     fun setOrphanLayerCallback(value: ((Layer) -> Boolean)?): LayerTraceEntryBuilder = apply {
90         this.orphanLayerCallback = value
91     }
92 
93     @JsName("setDuplicateLayerCallback")
94     fun setDuplicateLayerCallback(value: ((Layer) -> Boolean)): LayerTraceEntryBuilder = apply {
95         this.duplicateLayerCallback = value
96     }
97 
98     private fun notifyOrphansLayers() {
99         val callback = this.orphanLayerCallback ?: return
100 
101         // Fail if we find orphan layers.
102         orphans.forEach { orphan ->
103             // Workaround for b/141326137, ignore the existence of an orphan layer
104             if (callback.invoke(orphan)) {
105                 return@forEach
106             }
107             throw RuntimeException(
108                 ("Failed to parse layers trace. Found orphan layer with id = ${orphan.id}" +
109                     " with parentId = ${orphan.parentId}")
110             )
111         }
112     }
113 
114     /**
115      * Update the parent layers or each trace
116      *
117      * @return root layer
118      */
119     private fun updateParents() {
120         for (layer in layers.values) {
121             val parentId = layer.parentId
122 
123             val parentLayer = layers[parentId]
124             if (parentLayer == null) {
125                 orphans.add(layer)
126                 continue
127             }
128             parentLayer.addChild(layer)
129             layer.parent = parentLayer
130         }
131     }
132 
133     /**
134      * Update the parent layers or each trace
135      *
136      * @return root layer
137      */
138     private fun updateRelZParents() {
139         for (layer in layers.values) {
140             val parentId = layer.zOrderRelativeOfId
141 
142             val parentLayer = layers[parentId]
143             if (parentLayer == null) {
144                 layer.zOrderRelativeParentOf = parentId
145                 continue
146             }
147             layer.zOrderRelativeOf = parentLayer
148         }
149     }
150 
151     private fun computeRootLayers(): List<Layer> {
152         updateParents()
153         updateRelZParents()
154 
155         // Find all root layers (any sibling of the root layer is considered a root layer in the
156         // trace)
157         val rootLayers = mutableListOf<Layer>()
158 
159         // Getting the first orphan works because when dumping the layers, the root layer comes
160         // first, and given that orphans are added in the same order as the layers are provided
161         // in the first orphan layer should be the root layer.
162         if (orphans.isNotEmpty()) {
163             val firstRoot = orphans.first()
164             orphans.remove(firstRoot)
165             rootLayers.add(firstRoot)
166 
167             val remainingRoots = orphans.filter { it.parentId == firstRoot.parentId }
168             rootLayers.addAll(remainingRoots)
169 
170             // Remove RootLayers from orphans
171             orphans.removeAll(rootLayers)
172         }
173 
174         return rootLayers
175     }
176 
177     private fun filterOutLayersInVirtualDisplays(roots: List<Layer>): List<Layer> {
178         val physicalDisplays = displays.filterNot { it.isVirtual }.map { it.layerStackId }
179 
180         return roots.filter { physicalDisplays.contains(it.stackId) }
181     }
182 
183     private fun filterOutVirtualDisplays(displays: List<Display>): List<Display> {
184         return displays.filterNot { it.isVirtual }
185     }
186 
187     private fun filterOutLayersInOffDisplays(roots: List<Layer>): List<Layer> {
188         val offDisplays = displays.filter { it.isOff }.map { it.layerStackId }
189 
190         // Negated filtering because legacy traces do not contain any displays, so we don't want to
191         // remove all traces since displays will be empty, so we won't have any on or off displays
192         return roots.filterNot { offDisplays.contains(it.stackId) }
193     }
194 
195     private fun filterOutLayersStackMatchNoDisplay(roots: List<Layer>): List<Layer> {
196         val displayStacks = displays.map { it.layerStackId }
197         return roots.filter { displayStacks.contains(it.stackId) }
198     }
199 
200     /**
201      * Defines if virtual displays and the layers belonging to virtual displays (e.g., Screen
202      * Recording) should be ignored while parsing the entry
203      *
204      * @param ignore If the layers from virtual displays should be ignored or not
205      */
206     @JsName("ignoreVirtualDisplay")
207     fun ignoreVirtualDisplay(ignore: Boolean): LayerTraceEntryBuilder = apply {
208         this.ignoreVirtualDisplay = ignore
209     }
210 
211     /**
212      * Ignore layers whose stack ID doesn't match any display. This is the case, for example, when
213      * the device screen is off, or for layers that have not yet been removed after a display change
214      * (e.g., virtual screen recording display removed)
215      *
216      * @param ignore If the layers not matching any stack id should be removed or not
217      */
218     @JsName("ignoreLayersStackMatchNoDisplay")
219     fun ignoreLayersStackMatchNoDisplay(ignore: Boolean): LayerTraceEntryBuilder = apply {
220         this.ignoreLayersStackMatchNoDisplay = ignore
221     }
222 
223     /** Constructs the layer hierarchy from a flattened list of layers. */
224     @JsName("build")
225     fun build(): LayerTraceEntry {
226         val allRoots = computeRootLayers()
227         var filteredRoots = allRoots
228         var filteredDisplays = displays.toList()
229 
230         if (ignoreLayersStackMatchNoDisplay) {
231             filteredRoots = filterOutLayersStackMatchNoDisplay(filteredRoots)
232         }
233 
234         if (ignoreVirtualDisplay) {
235             filteredRoots = filterOutLayersInVirtualDisplays(filteredRoots)
236             filteredDisplays = filterOutVirtualDisplays(filteredDisplays)
237         }
238 
239         filteredRoots = filterOutLayersInOffDisplays(filteredRoots)
240 
241         // Fail if we find orphan layers.
242         notifyOrphansLayers()
243 
244         return LayerTraceEntry(
245             elapsedTimestamp,
246             realTimestamp,
247             hwcBlob,
248             where,
249             filteredDisplays.toTypedArray(),
250             vSyncId,
251             filteredRoots.toTypedArray(),
252         )
253     }
254 }
255