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