1 /* 2 * Copyright (C) 2022 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.window; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UiThread; 22 import android.os.Handler; 23 import android.os.Looper; 24 import android.util.ArraySet; 25 import android.util.Log; 26 import android.util.SparseArray; 27 import android.view.SurfaceControl.Transaction; 28 import android.view.SurfaceView; 29 import android.view.View; 30 import android.view.ViewRootImpl; 31 32 import com.android.internal.annotations.GuardedBy; 33 34 import java.util.Set; 35 import java.util.function.Consumer; 36 import java.util.function.Supplier; 37 38 /** 39 * Used to organize syncs for surfaces. 40 * 41 * The SurfaceSyncer allows callers to add desired syncs into a set and wait for them to all 42 * complete before getting a callback. The purpose of the Syncer is to be an accounting mechanism 43 * so each sync implementation doesn't need to handle it themselves. The Syncer class is used the 44 * following way. 45 * 46 * 1. {@link #setupSync(Runnable)} is called 47 * 2. {@link #addToSync(int, SyncTarget)} is called for every SyncTarget object that wants to be 48 * included in the sync. If the addSync is called for a View or SurfaceView it needs to be called 49 * on the UI thread. When addToSync is called, it's guaranteed that any UI updates that were 50 * requested before addToSync but after the last frame drew, will be included in the sync. 51 * 3. {@link #markSyncReady(int)} should be called when all the {@link SyncTarget}s have been added 52 * to the SyncSet. Now the SyncSet is closed and no more SyncTargets can be added to it. 53 * 4. The SyncSet will gather the data for each SyncTarget using the steps described below. When 54 * all the SyncTargets have finished, the syncRequestComplete will be invoked and the transaction 55 * will either be applied or sent to the caller. In most cases, only the SurfaceSyncer should be 56 * handling the Transaction object directly. However, there are some cases where the framework 57 * needs to send the Transaction elsewhere, like in ViewRootImpl, so that option is provided. 58 * 59 * The following is what happens within the {@link SyncSet} 60 * 1. Each SyncableTarget will get a {@link SyncTarget#onReadyToSync} callback that contains 61 * a {@link SyncBufferCallback}. 62 * 2. Each {@link SyncTarget} needs to invoke {@link SyncBufferCallback#onBufferReady(Transaction)}. 63 * This makes sure the SyncSet knows when the SyncTarget is complete, allowing the SyncSet to get 64 * the Transaction that contains the buffer. 65 * 3. When the final SyncBufferCallback finishes for the SyncSet, the syncRequestComplete Consumer 66 * will be invoked with the transaction that contains all information requested in the sync. This 67 * could include buffers and geometry changes. The buffer update will include the UI changes that 68 * were requested for the View. 69 * 70 * @hide 71 */ 72 public class SurfaceSyncer { 73 private static final String TAG = "SurfaceSyncer"; 74 private static final boolean DEBUG = false; 75 76 private static Supplier<Transaction> sTransactionFactory = Transaction::new; 77 78 private final Object mSyncSetLock = new Object(); 79 @GuardedBy("mSyncSetLock") 80 private final SparseArray<SyncSet> mSyncSets = new SparseArray<>(); 81 @GuardedBy("mSyncSetLock") 82 private int mIdCounter = 0; 83 84 /** 85 * @hide 86 */ setTransactionFactory(Supplier<Transaction> transactionFactory)87 public static void setTransactionFactory(Supplier<Transaction> transactionFactory) { 88 sTransactionFactory = transactionFactory; 89 } 90 91 /** 92 * Starts a sync and will automatically apply the final, merged transaction. 93 * 94 * @param onComplete The runnable that is invoked when the sync has completed. This will run on 95 * the same thread that the sync was started on. 96 * @return The syncId for the newly created sync. 97 * @see #setupSync(Consumer) 98 */ setupSync(@ullable Runnable onComplete)99 public int setupSync(@Nullable Runnable onComplete) { 100 Handler handler = new Handler(Looper.myLooper()); 101 return setupSync(transaction -> { 102 transaction.apply(); 103 if (onComplete != null) { 104 handler.post(onComplete); 105 } 106 }); 107 } 108 109 /** 110 * Starts a sync. 111 * 112 * @param syncRequestComplete The complete callback that contains the syncId and transaction 113 * with all the sync data merged. 114 * @return The syncId for the newly created sync. 115 * @hide 116 * @see #setupSync(Runnable) 117 */ setupSync(@onNull Consumer<Transaction> syncRequestComplete)118 public int setupSync(@NonNull Consumer<Transaction> syncRequestComplete) { 119 synchronized (mSyncSetLock) { 120 final int syncId = mIdCounter++; 121 if (DEBUG) { 122 Log.d(TAG, "setupSync " + syncId); 123 } 124 SyncSet syncSet = new SyncSet(syncId, transaction -> { 125 synchronized (mSyncSetLock) { 126 mSyncSets.remove(syncId); 127 } 128 syncRequestComplete.accept(transaction); 129 }); 130 mSyncSets.put(syncId, syncSet); 131 return syncId; 132 } 133 } 134 135 /** 136 * Mark the sync set as ready to complete. No more data can be added to the specified syncId. 137 * Once the sync set is marked as ready, it will be able to complete once all Syncables in the 138 * set have completed their sync 139 * 140 * @param syncId The syncId to mark as ready. 141 */ markSyncReady(int syncId)142 public void markSyncReady(int syncId) { 143 SyncSet syncSet; 144 synchronized (mSyncSetLock) { 145 syncSet = mSyncSets.get(syncId); 146 } 147 if (syncSet == null) { 148 Log.e(TAG, "Failed to find syncSet for syncId=" + syncId); 149 return; 150 } 151 syncSet.markSyncReady(); 152 } 153 154 /** 155 * Merge another SyncSet into the specified syncId. 156 * @param syncId The current syncId to merge into 157 * @param otherSyncId The other syncId to be merged 158 * @param otherSurfaceSyncer The other SurfaceSyncer where the otherSyncId is from 159 */ merge(int syncId, int otherSyncId, SurfaceSyncer otherSurfaceSyncer)160 public void merge(int syncId, int otherSyncId, SurfaceSyncer otherSurfaceSyncer) { 161 SyncSet syncSet; 162 synchronized (mSyncSetLock) { 163 syncSet = mSyncSets.get(syncId); 164 } 165 166 SyncSet otherSyncSet = otherSurfaceSyncer.getAndValidateSyncSet(otherSyncId); 167 if (otherSyncSet == null) { 168 return; 169 } 170 171 if (DEBUG) { 172 Log.d(TAG, 173 "merge id=" + otherSyncId + " from=" + otherSurfaceSyncer + " into id=" + syncId 174 + " from" + this); 175 } 176 syncSet.merge(otherSyncSet); 177 } 178 179 /** 180 * Add a SurfaceView to a sync set. This is different than {@link #addToSync(int, View)} because 181 * it requires the caller to notify the start and finish drawing in order to sync. 182 * 183 * @param syncId The syncId to add an entry to. 184 * @param surfaceView The SurfaceView to add to the sync. 185 * @param frameCallbackConsumer The callback that's invoked to allow the caller to notify the 186 * Syncer when the SurfaceView has started drawing and finished. 187 * 188 * @return true if the SurfaceView was successfully added to the SyncSet, false otherwise. 189 */ 190 @UiThread addToSync(int syncId, SurfaceView surfaceView, Consumer<SurfaceViewFrameCallback> frameCallbackConsumer)191 public boolean addToSync(int syncId, SurfaceView surfaceView, 192 Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) { 193 return addToSync(syncId, new SurfaceViewSyncTarget(surfaceView, frameCallbackConsumer)); 194 } 195 196 /** 197 * Add a View's rootView to a sync set. 198 * 199 * @param syncId The syncId to add an entry to. 200 * @param view The view where the root will be add to the sync set 201 * 202 * @return true if the View was successfully added to the SyncSet, false otherwise. 203 */ 204 @UiThread addToSync(int syncId, @NonNull View view)205 public boolean addToSync(int syncId, @NonNull View view) { 206 ViewRootImpl viewRoot = view.getViewRootImpl(); 207 if (viewRoot == null) { 208 return false; 209 } 210 return addToSync(syncId, viewRoot.mSyncTarget); 211 } 212 213 /** 214 * Add a {@link SyncTarget} to a sync set. The sync set will wait for all 215 * SyncableSurfaces to complete before notifying. 216 * 217 * @param syncId The syncId to add an entry to. 218 * @param syncTarget A SyncableSurface that implements how to handle syncing 219 * buffers. 220 * 221 * @return true if the SyncTarget was successfully added to the SyncSet, false otherwise. 222 */ addToSync(int syncId, @NonNull SyncTarget syncTarget)223 public boolean addToSync(int syncId, @NonNull SyncTarget syncTarget) { 224 SyncSet syncSet = getAndValidateSyncSet(syncId); 225 if (syncSet == null) { 226 return false; 227 } 228 if (DEBUG) { 229 Log.d(TAG, "addToSync id=" + syncId); 230 } 231 return syncSet.addSyncableSurface(syncTarget); 232 } 233 234 /** 235 * Add a transaction to a specific sync so it can be merged along with the frames from the 236 * Syncables in the set. This is so the caller can add arbitrary transaction info that will be 237 * applied at the same time as the buffers 238 * @param syncId The syncId where the transaction will be merged to. 239 * @param t The transaction to merge in the sync set. 240 */ addTransactionToSync(int syncId, Transaction t)241 public void addTransactionToSync(int syncId, Transaction t) { 242 SyncSet syncSet = getAndValidateSyncSet(syncId); 243 if (syncSet != null) { 244 syncSet.addTransactionToSync(t); 245 } 246 } 247 getAndValidateSyncSet(int syncId)248 private SyncSet getAndValidateSyncSet(int syncId) { 249 SyncSet syncSet; 250 synchronized (mSyncSetLock) { 251 syncSet = mSyncSets.get(syncId); 252 } 253 if (syncSet == null) { 254 Log.e(TAG, "Failed to find sync for id=" + syncId); 255 return null; 256 } 257 return syncSet; 258 } 259 260 /** 261 * A SyncTarget that can be added to a sync set. 262 */ 263 public interface SyncTarget { 264 /** 265 * Called when the Syncable is ready to begin handing a sync request. When invoked, the 266 * implementor is required to call {@link SyncBufferCallback#onBufferReady(Transaction)} 267 * and {@link SyncBufferCallback#onBufferReady(Transaction)} in order for this Syncable 268 * to be marked as complete. 269 * 270 * Always invoked on the thread that initiated the call to 271 * {@link #addToSync(int, SyncTarget)} 272 * 273 * @param syncBufferCallback A SyncBufferCallback that the caller must invoke onBufferReady 274 */ onReadyToSync(SyncBufferCallback syncBufferCallback)275 void onReadyToSync(SyncBufferCallback syncBufferCallback); 276 277 /** 278 * There's no guarantee about the thread this callback is invoked on. 279 */ onSyncComplete()280 default void onSyncComplete() {} 281 } 282 283 /** 284 * Interface so the SurfaceSyncer can know when it's safe to start and when everything has been 285 * completed. The caller should invoke the calls when the rendering has started and finished a 286 * frame. 287 */ 288 public interface SyncBufferCallback { 289 /** 290 * Invoked when the transaction contains the buffer and is ready to sync. 291 * 292 * @param t The transaction that contains the buffer to be synced. This can be null if 293 * there's nothing to sync 294 */ onBufferReady(@ullable Transaction t)295 void onBufferReady(@Nullable Transaction t); 296 } 297 298 /** 299 * Class that collects the {@link SyncTarget}s and notifies when all the surfaces have 300 * a frame ready. 301 */ 302 private static class SyncSet { 303 private final Object mLock = new Object(); 304 305 @GuardedBy("mLock") 306 private final Set<Integer> mPendingSyncs = new ArraySet<>(); 307 @GuardedBy("mLock") 308 private final Transaction mTransaction = sTransactionFactory.get(); 309 @GuardedBy("mLock") 310 private boolean mSyncReady; 311 @GuardedBy("mLock") 312 private final Set<SyncTarget> mSyncTargets = new ArraySet<>(); 313 314 private final int mSyncId; 315 @GuardedBy("mLock") 316 private Consumer<Transaction> mSyncRequestCompleteCallback; 317 318 @GuardedBy("mLock") 319 private final Set<SyncSet> mMergedSyncSets = new ArraySet<>(); 320 321 @GuardedBy("mLock") 322 private boolean mFinished; 323 SyncSet(int syncId, Consumer<Transaction> syncRequestComplete)324 private SyncSet(int syncId, Consumer<Transaction> syncRequestComplete) { 325 mSyncId = syncId; 326 mSyncRequestCompleteCallback = syncRequestComplete; 327 } 328 addSyncableSurface(SyncTarget syncTarget)329 boolean addSyncableSurface(SyncTarget syncTarget) { 330 SyncBufferCallback syncBufferCallback = new SyncBufferCallback() { 331 @Override 332 public void onBufferReady(Transaction t) { 333 synchronized (mLock) { 334 if (t != null) { 335 mTransaction.merge(t); 336 } 337 mPendingSyncs.remove(hashCode()); 338 checkIfSyncIsComplete(); 339 } 340 } 341 }; 342 343 synchronized (mLock) { 344 if (mSyncReady) { 345 Log.e(TAG, "Sync " + mSyncId + " was already marked as ready. No more " 346 + "SyncTargets can be added."); 347 return false; 348 } 349 mPendingSyncs.add(syncBufferCallback.hashCode()); 350 mSyncTargets.add(syncTarget); 351 } 352 syncTarget.onReadyToSync(syncBufferCallback); 353 return true; 354 } 355 markSyncReady()356 void markSyncReady() { 357 synchronized (mLock) { 358 mSyncReady = true; 359 checkIfSyncIsComplete(); 360 } 361 } 362 363 @GuardedBy("mLock") checkIfSyncIsComplete()364 private void checkIfSyncIsComplete() { 365 if (!mSyncReady || !mPendingSyncs.isEmpty() || !mMergedSyncSets.isEmpty()) { 366 if (DEBUG) { 367 Log.d(TAG, "Syncable is not complete. mSyncReady=" + mSyncReady 368 + " mPendingSyncs=" + mPendingSyncs.size() + " mergedSyncs=" 369 + mMergedSyncSets.size()); 370 } 371 return; 372 } 373 374 if (DEBUG) { 375 Log.d(TAG, "Successfully finished sync id=" + mSyncId); 376 } 377 378 for (SyncTarget syncTarget : mSyncTargets) { 379 syncTarget.onSyncComplete(); 380 } 381 mSyncTargets.clear(); 382 mSyncRequestCompleteCallback.accept(mTransaction); 383 mFinished = true; 384 } 385 386 /** 387 * Add a Transaction to this sync set. This allows the caller to provide other info that 388 * should be synced with the buffers. 389 */ addTransactionToSync(Transaction t)390 void addTransactionToSync(Transaction t) { 391 synchronized (mLock) { 392 mTransaction.merge(t); 393 } 394 } 395 updateCallback(Consumer<Transaction> transactionConsumer)396 public void updateCallback(Consumer<Transaction> transactionConsumer) { 397 synchronized (mLock) { 398 if (mFinished) { 399 Log.e(TAG, "Attempting to merge SyncSet " + mSyncId + " when sync is" 400 + " already complete"); 401 transactionConsumer.accept(new Transaction()); 402 } 403 404 final Consumer<Transaction> oldCallback = mSyncRequestCompleteCallback; 405 mSyncRequestCompleteCallback = transaction -> { 406 oldCallback.accept(new Transaction()); 407 transactionConsumer.accept(transaction); 408 }; 409 } 410 } 411 412 /** 413 * Merge a SyncSet into this SyncSet. Since SyncSets could still have pending SyncTargets, 414 * we need to make sure those can still complete before the mergeTo syncSet is considered 415 * complete. 416 * 417 * We keep track of all the merged SyncSets until they are marked as done, and then they 418 * are removed from the set. This SyncSet is not considered done until all the merged 419 * SyncSets are done. 420 * 421 * When the merged SyncSet is complete, it will invoke the original syncRequestComplete 422 * callback but send an empty transaction to ensure the changes are applied early. This 423 * is needed in case the original sync is relying on the callback to continue processing. 424 * 425 * @param otherSyncSet The other SyncSet to merge into this one. 426 */ merge(SyncSet otherSyncSet)427 public void merge(SyncSet otherSyncSet) { 428 synchronized (mLock) { 429 mMergedSyncSets.add(otherSyncSet); 430 } 431 otherSyncSet.updateCallback(transaction -> { 432 synchronized (mLock) { 433 mMergedSyncSets.remove(otherSyncSet); 434 mTransaction.merge(transaction); 435 checkIfSyncIsComplete(); 436 } 437 }); 438 } 439 } 440 441 /** 442 * Wrapper class to help synchronize SurfaceViews 443 */ 444 private static class SurfaceViewSyncTarget implements SyncTarget { 445 private final SurfaceView mSurfaceView; 446 private final Consumer<SurfaceViewFrameCallback> mFrameCallbackConsumer; 447 SurfaceViewSyncTarget(SurfaceView surfaceView, Consumer<SurfaceViewFrameCallback> frameCallbackConsumer)448 SurfaceViewSyncTarget(SurfaceView surfaceView, 449 Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) { 450 mSurfaceView = surfaceView; 451 mFrameCallbackConsumer = frameCallbackConsumer; 452 } 453 454 @Override onReadyToSync(SyncBufferCallback syncBufferCallback)455 public void onReadyToSync(SyncBufferCallback syncBufferCallback) { 456 mFrameCallbackConsumer.accept( 457 () -> mSurfaceView.syncNextFrame(syncBufferCallback::onBufferReady)); 458 } 459 } 460 461 /** 462 * A frame callback that is used to synchronize SurfaceViews. The owner of the SurfaceView must 463 * implement onFrameStarted when trying to sync the SurfaceView. This is to ensure the sync 464 * knows when the frame is ready to add to the sync. 465 */ 466 public interface SurfaceViewFrameCallback { 467 /** 468 * Called when the SurfaceView is going to render a frame 469 */ onFrameStarted()470 void onFrameStarted(); 471 } 472 } 473