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