Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ea372b7f authored by chaviw's avatar chaviw
Browse files

Refactor SurfaceSyncer to use SurfaceSyncGroups instead of syncId

There's no need for SurfaceSyncer to store the SyncSets and only return
a syncId. The callers can instead own the SyncGroups and call methods
directly on them. This makes the code more readable and cleans up Syncer
since you can directly call methods instead of piping through an outer
method.

Test: SurfaceSyncGroupContinuousTest
Test: SurfaceSyncGroupTest
Change-Id: I82976e26e10e20d2752e6be24a45069a6a64e849
Bug: 237804605
parent c9c081c8
Loading
Loading
Loading
Loading
+9 −1
Original line number Original line Diff line number Diff line
@@ -17,10 +17,10 @@ package android.view;


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UiThread;
import android.annotation.UiThread;
import android.graphics.Region;
import android.graphics.Region;
import android.hardware.HardwareBuffer;
import android.hardware.HardwareBuffer;
import android.window.SurfaceSyncGroup;


/**
/**
 * Provides an interface to the root-Surface of a View Hierarchy or Window. This
 * Provides an interface to the root-Surface of a View Hierarchy or Window. This
@@ -138,4 +138,12 @@ public interface AttachedSurfaceControl {
     */
     */
    default void setTouchableRegion(@Nullable Region r) {
    default void setTouchableRegion(@Nullable Region r) {
    }
    }

    /**
     * Returns a SyncTarget that can be used to sync {@link AttachedSurfaceControl} in a
     * {@link SurfaceSyncGroup}
     *
     * @hide
     */
    SurfaceSyncGroup.SyncTarget getSyncTarget();
}
}
+14 −14
Original line number Original line Diff line number Diff line
@@ -47,7 +47,7 @@ import android.util.Log;
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceControl.Transaction;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
import android.window.SurfaceSyncer;
import android.window.SurfaceSyncGroup;


import com.android.internal.view.SurfaceCallbackHelper;
import com.android.internal.view.SurfaceCallbackHelper;


@@ -206,8 +206,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall


    private int mSurfaceFlags = SurfaceControl.HIDDEN;
    private int mSurfaceFlags = SurfaceControl.HIDDEN;


    private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer();
    private final ArraySet<SurfaceSyncGroup> mSyncGroups = new ArraySet<>();
    private final ArraySet<Integer> mSyncIds = new ArraySet<>();


    /**
    /**
     * Transaction that should be used from the render thread. This transaction is only thread safe
     * Transaction that should be used from the render thread. This transaction is only thread safe
@@ -1069,20 +1068,21 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
    }
    }


    private void handleSyncNoBuffer(SurfaceHolder.Callback[] callbacks) {
    private void handleSyncNoBuffer(SurfaceHolder.Callback[] callbacks) {
        final int syncId = mSurfaceSyncer.setupSync(this::onDrawFinished);
        final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
        synchronized (mSyncGroups) {
            mSyncGroups.add(syncGroup);
        }


        mSurfaceSyncer.addToSync(syncId, syncBufferCallback -> redrawNeededAsync(callbacks,
        syncGroup.addToSync(syncBufferCallback -> redrawNeededAsync(callbacks,
                () -> {
                () -> {
                    syncBufferCallback.onBufferReady(null);
                    syncBufferCallback.onBufferReady(null);
                    synchronized (mSyncIds) {
                    onDrawFinished();
                        mSyncIds.remove(syncId);
                    synchronized (mSyncGroups) {
                        mSyncGroups.remove(syncGroup);
                    }
                    }
                }));
                }));


        mSurfaceSyncer.markSyncReady(syncId);
        syncGroup.markSyncReady();
        synchronized (mSyncIds) {
            mSyncIds.add(syncId);
        }
    }
    }


    private void redrawNeededAsync(SurfaceHolder.Callback[] callbacks,
    private void redrawNeededAsync(SurfaceHolder.Callback[] callbacks,
@@ -1098,9 +1098,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
    public void surfaceSyncStarted() {
    public void surfaceSyncStarted() {
        ViewRootImpl viewRoot = getViewRootImpl();
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null) {
        if (viewRoot != null) {
            synchronized (mSyncIds) {
            synchronized (mSyncGroups) {
                for (int syncId : mSyncIds) {
                for (SurfaceSyncGroup syncGroup : mSyncGroups) {
                    viewRoot.mergeSync(syncId, mSurfaceSyncer);
                    viewRoot.mergeSync(syncGroup);
                }
                }
            }
            }
        }
        }
+26 −20
Original line number Original line Diff line number Diff line
@@ -197,7 +197,7 @@ import android.window.ClientWindowFrames;
import android.window.CompatOnBackInvokedCallback;
import android.window.CompatOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import android.window.OnBackInvokedDispatcher;
import android.window.SurfaceSyncer;
import android.window.SurfaceSyncGroup;
import android.window.WindowOnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;


import com.android.internal.R;
import com.android.internal.R;
@@ -829,9 +829,8 @@ public final class ViewRootImpl implements ViewParent,
        return mHandwritingInitiator;
        return mHandwritingInitiator;
    }
    }


    private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer();
    private SurfaceSyncGroup mSyncGroup;
    private int mSyncId = UNSET_SYNC_ID;
    private SurfaceSyncGroup.SyncBufferCallback mSyncBufferCallback;
    private SurfaceSyncer.SyncBufferCallback mSyncBufferCallback;
    private int mNumSyncsInProgress = 0;
    private int mNumSyncsInProgress = 0;


    private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
    private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
@@ -3594,8 +3593,8 @@ public final class ViewRootImpl implements ViewParent,
            mSyncBufferCallback = null;
            mSyncBufferCallback = null;
            mSyncBuffer = false;
            mSyncBuffer = false;
            if (isInLocalSync()) {
            if (isInLocalSync()) {
                mSurfaceSyncer.markSyncReady(mSyncId);
                mSyncGroup.markSyncReady();
                mSyncId = UNSET_SYNC_ID;
                mSyncGroup = null;
            }
            }
        }
        }
    }
    }
@@ -3607,17 +3606,19 @@ public final class ViewRootImpl implements ViewParent,
        }
        }


        final int seqId = mSyncSeqId;
        final int seqId = mSyncSeqId;
        mSyncId = mSurfaceSyncer.setupSync(transaction -> {
        mSyncGroup = new SurfaceSyncGroup(transaction -> {
            // Callback will be invoked on executor thread so post to main thread.
            // Callback will be invoked on executor thread so post to main thread.
            mHandler.postAtFrontOfQueue(() -> {
            mHandler.postAtFrontOfQueue(() -> {
                if (transaction != null) {
                    mSurfaceChangedTransaction.merge(transaction);
                    mSurfaceChangedTransaction.merge(transaction);
                }
                reportDrawFinished(seqId);
                reportDrawFinished(seqId);
            });
            });
        });
        });
        if (DEBUG_BLAST) {
        if (DEBUG_BLAST) {
            Log.d(mTag, "Setup new sync id=" + mSyncId);
            Log.d(mTag, "Setup new sync id=" + mSyncGroup);
        }
        }
        mSurfaceSyncer.addToSync(mSyncId, mSyncTarget);
        mSyncGroup.addToSync(mSyncTarget);
        notifySurfaceSyncStarted();
        notifySurfaceSyncStarted();
    }
    }


@@ -4255,11 +4256,11 @@ public final class ViewRootImpl implements ViewParent,
        return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
        return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
    }
    }


    void addToSync(SurfaceSyncer.SyncTarget syncable) {
    void addToSync(SurfaceSyncGroup.SyncTarget syncable) {
        if (!isInLocalSync()) {
        if (!isInLocalSync()) {
            return;
            return;
        }
        }
        mSurfaceSyncer.addToSync(mSyncId, syncable);
        mSyncGroup.addToSync(syncable);
    }
    }


    /**
    /**
@@ -4267,7 +4268,7 @@ public final class ViewRootImpl implements ViewParent,
     * within VRI.
     * within VRI.
     */
     */
    public boolean isInLocalSync() {
    public boolean isInLocalSync() {
        return mSyncId != UNSET_SYNC_ID;
        return mSyncGroup != null;
    }
    }


    private void addFrameCommitCallbackIfNeeded() {
    private void addFrameCommitCallbackIfNeeded() {
@@ -4392,7 +4393,7 @@ public final class ViewRootImpl implements ViewParent,
            }
            }


            if (mSurfaceHolder != null && mSurface.isValid()) {
            if (mSurfaceHolder != null && mSurface.isValid()) {
                final SurfaceSyncer.SyncBufferCallback syncBufferCallback = mSyncBufferCallback;
                final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback = mSyncBufferCallback;
                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
                        mHandler.post(() -> syncBufferCallback.onBufferReady(null)));
                        mHandler.post(() -> syncBufferCallback.onBufferReady(null)));
                mSyncBufferCallback = null;
                mSyncBufferCallback = null;
@@ -10929,7 +10930,7 @@ public final class ViewRootImpl implements ViewParent,
    }
    }


    private void registerCallbacksForSync(boolean syncBuffer,
    private void registerCallbacksForSync(boolean syncBuffer,
            final SurfaceSyncer.SyncBufferCallback syncBufferCallback) {
            final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
        if (!isHardwareEnabled()) {
        if (!isHardwareEnabled()) {
            return;
            return;
        }
        }
@@ -11002,9 +11003,9 @@ public final class ViewRootImpl implements ViewParent,
        });
        });
    }
    }


    public final SurfaceSyncer.SyncTarget mSyncTarget = new SurfaceSyncer.SyncTarget() {
    public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() {
        @Override
        @Override
        public void onReadyToSync(SurfaceSyncer.SyncBufferCallback syncBufferCallback) {
        public void onReadyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
            readyToSync(syncBufferCallback);
            readyToSync(syncBufferCallback);
        }
        }


@@ -11018,7 +11019,12 @@ public final class ViewRootImpl implements ViewParent,
        }
        }
    };
    };


    private void readyToSync(SurfaceSyncer.SyncBufferCallback syncBufferCallback) {
    @Override
    public SurfaceSyncGroup.SyncTarget getSyncTarget() {
        return mSyncTarget;
    }

    private void readyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
        mNumSyncsInProgress++;
        mNumSyncsInProgress++;
        if (!isInLocalSync()) {
        if (!isInLocalSync()) {
            // Always sync the buffer if the sync request did not come from VRI.
            // Always sync the buffer if the sync request did not come from VRI.
@@ -11041,10 +11047,10 @@ public final class ViewRootImpl implements ViewParent,
        }
        }
    }
    }


    void mergeSync(int syncId, SurfaceSyncer otherSyncer) {
    void mergeSync(SurfaceSyncGroup otherSyncGroup) {
        if (!isInLocalSync()) {
        if (!isInLocalSync()) {
            return;
            return;
        }
        }
        mSurfaceSyncer.merge(mSyncId, syncId, otherSyncer);
        mSyncGroup.merge(otherSyncGroup);
    }
    }
}
}
+86 −0
Original line number Original line Diff line number Diff line
## SurfaceSyncer
## SurfaceSyncGroup


### Overview
### Overview


@@ -6,30 +6,36 @@ A generic way for data to be gathered so multiple surfaces can be synced. This i


### Code
### Code


SurfaceSyncer is a class that manages sync requests and reports back when all participants in the sync are ready.
SurfaceSyncGroup is a class that manages sync requests and reports back when all participants in the sync are ready.


##### setupSync
##### Constructor
The first step is to create a sync request. This is done by calling `setupSync`.
The first step is to create a sync request. This is done by creating a new `SurfaceSyncGroup`.
There are two setupSync methods: one that accepts a `Runnable` and one that accepts a `Consumer<Transaction>`. The `setupSync(Runnable)` will automatically apply the final transaction and invokes the Runnable to notify the caller that the sync is complete. The `Runnable` can be null since not all cases care about the sync being complete. The second `setupSync(Consumer<Transaction>)` should only be used by ViewRootImpl. The purpose of this one is to allow the caller to get back the merged transaction without it being applied. ViewRootImpl uses it to send the transaction to WindowManagerService to be synced there. Using this one for other cases is unsafe because the caller may hold the transaction longer than expected and prevent buffers from being latched and released.
There are two constructors: one that accepts a `Consumer<Transaction>` and one that's empty. The empty constructor will automatically apply the final transaction. The second constructor should only be used by ViewRootImpl. The purpose of this one is to allow the caller to get back the merged transaction without it being applied. ViewRootImpl uses it to send the transaction to WindowManagerService to be synced there. Using this one for other cases is unsafe because the caller may hold the transaction longer than expected and prevent buffers from being latched and released.

The setupSync returns a syncId which is used for the other APIs


##### markSyncReady
##### markSyncReady


When the caller has added all the `SyncTarget` to the sync, they should call `markSyncReady(syncId)` If the caller doesn't call this, the sync will never complete since the SurfaceSyncer wants to give the caller a chance to add all the SyncTargets before considering the sync ready. Before `markSyncReady` is called, the `SyncTargets` can actually produce a frame, which will just be held in a transaction until all other `SyncTargets` are ready AND `markSyncReady` has been called. Once markSyncReady has been called, you cannot add any more `SyncTargets` to that particular syncId.
When the caller has added all the `SyncTarget` to the sync, they should call `markSyncReady()` If the caller doesn't call this, the sync will never complete since the SurfaceSyncGroup wants to give the caller a chance to add all the SyncTargets before considering the sync ready. Before `markSyncReady` is called, the `SyncTargets` can actually produce a frame, which will just be held in a transaction until all other `SyncTargets` are ready AND `markSyncReady` has been called. Once markSyncReady has been called, you cannot add any more `SyncTargets` to that particular SurfaceSyncGroup.


##### addToSync
##### addToSync


The caller will invoke `addToSync` for every `SyncTarget` that it wants included. The caller must specify which syncId they want to add the `SyncTarget` too since there can be multiple syncs open at the same time. There are a few helper methods since the most common cases are Views and SurfaceView
The caller will invoke `addToSync` for every `SyncTarget` that it wants included. There are a few helper methods since the most common cases are Views and SurfaceView
* `addToSync(int syncId, View)` - This is used for syncing the root of the View, specificially the ViewRootImpl
* `addToSync(AttachedSurfaceControl)` - This is used for syncing the root of the View, specificially the ViewRootImpl
* `addToSync(int syncId, SurfaceView, Consumer<SurfaceViewFrameCallback>)` - This is to sync a SurfaceView. Since SurfaceViews are rendered by the app, the caller will be expected to provide a way to get back the buffer to sync. More details about that [below](#surfaceviewframecallback)
* `addToSync(SurfaceView, Consumer<SurfaceViewFrameCallback>)` - This is to sync a SurfaceView. Since SurfaceViews are rendered by the app, the caller will be expected to provide a way to get back the buffer to sync. More details about that [below](#surfaceviewframecallback)
* `addToSync(int syncId, SyncTarget)` - This is the generic method. It can be used to sync arbitrary info. The SyncTarget interface has required methods that need to be implemented to properly get the transaction to sync.
* `addToSync(SyncTarget)` - This is the generic method. It can be used to sync arbitrary info. The SyncTarget interface has required methods that need to be implemented to properly get the transaction to sync.


When calling addToSync with either View or SurfaceView, it must occur on the UI Thread. This is to ensure consistent behavior, where any UI changes done while still on the UI thread are included in this frame. The next vsync will pick up those changes and request to draw.
When calling addToSync with either AttachedSurfaceControl or SurfaceView, it must be called on the UI Thread. This is to ensure consistent behavior, where any UI changes done while still on the UI thread are included in this frame. The next vsync will pick up those changes and request to draw.


##### addTransactionToSync
##### addTransactionToSync


This is a simple method that allows callers to add generic Transactions to the sync. The caller invokes `addTransactionToSync(int syncId, Transaction)`. This can be used for things that need to be synced with content, but aren't updating content themselves.
This is a simple method that allows callers to add generic Transactions to the sync. The caller invokes `addTransactionToSync(Transaction)`. This can be used for any additional things that need to be included in the same SyncGroup.

##### merge

To add more flexibility to Syncs, an API is provided to merge SurfaceSyncGroups. The caller provides the SurfaceSyncGroup it wants merged. The current SurfaceSyncGroup will now wait for the passed in SurfaceSyncGroup to complete, as well as its own SyncTargets to complete before invoking the callback. The passed in SurfaceSyncGroup will also get a complete callback but only when its SurfaceSyncGroup completes, not the one it merged into. If a `Consumer<Transaction>` was passed in to the SurfaceSyncGroup, it will get back an emtpy Transaction so it can't accidentally apply things that were meant to be merged.

##### addSyncCompleteCallback

This allows callers to receive a callback when the sync is complete. The method takes in an Executor and a Runnable that will be invoked when the SurfaceSyncGroup has completed. The Executor is used to invoke the callback on the desired thread. You can add more than one callback.


##### SyncTarget
##### SyncTarget


@@ -45,7 +51,7 @@ This interface is used to tell the sync that this SyncTarget is ready. There's o


##### SurfaceViewFrameCallback
##### SurfaceViewFrameCallback


As mentioned above, SurfaceViews are a special case because the buffers produced are handled by the app, and not the framework. Because of this, the syncer doesn't know which frame to sync. Therefore, to sync SurfaceViews, the caller must provide a way to notify the syncer that it's going to render a buffer and that this next buffer is the one to sync. The `SurfaceViewFrameCallback` has one method `onFrameStarted()`. When this is invoked, the Syncer sets up a request to sync the next buffer for the SurfaceView.
As mentioned above, SurfaceViews are a special case because the buffers produced are handled by the app, and not the framework. Because of this, the SurfaceSyncGroup doesn't know which frame to sync. Therefore, to sync SurfaceViews, the caller must provide a way to notify the SurfaceSyncGroup that it's going to render a buffer and that this next buffer is the one to sync. The `SurfaceViewFrameCallback` has one method `onFrameStarted()`. When this is invoked, the SurfaceSyncGroup sets up a request to sync the next buffer for the SurfaceView.




### Example
### Example
@@ -53,28 +59,28 @@ As mentioned above, SurfaceViews are a special case because the buffers produced
A simple example where you want to sync two windows and also include a transaction in the sync
A simple example where you want to sync two windows and also include a transaction in the sync


```java
```java
SurfaceSyncer syncer = new SurfaceSyncer();
SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
int syncId = syncer.setupSync(() -> {
SyncGroup.addSyncCompleteCallback(mMainThreadExecutor, () -> {
    Log.d(TAG, "syncComplete");
    Log.d(TAG, "syncComplete");
};
};
syncer.addToSync(syncId, view1);
syncGroup.addToSync(view1.getRootSurfaceControl());
syncer.addToSync(syncId, view2);
syncGroup.addToSync(view2.getRootSurfaceControl());
syncer.addTransactionToSync(syncId, transaction);
syncGroup.addTransactionToSync(transaction);
syncer.markSyncReady(syncId);
syncGroup.markSyncReady();
```
```


A SurfaceView example:
A SurfaceView example:
See `frameworks/base/tests/SurfaceViewSyncTest` for a working example
See `frameworks/base/tests/SurfaceViewSyncTest` for a working example


```java
```java
SurfaceSyncer syncer = new SurfaceSyncer();
SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
int syncId = syncer.setupSync(() -> {
syncGroup.addSyncCompleteCallback(mMainThreadExecutor, () -> {
    Log.d(TAG, "syncComplete");
    Log.d(TAG, "syncComplete");
};
};
syncer.addToSync(syncId, container);
syncGroup.addToSync(container.getRootSurfaceControl());
syncer.addToSync(syncId, surfaceView, frameCallback -> {
syncGroup.addToSync(surfaceView, frameCallback -> {
    // Call this when the SurfaceView is ready to render a new frame with the changes.
    // Call this when the SurfaceView is ready to render a new frame with the changes.
    frameCallback.onFrameStarted()
    frameCallback.onFrameStarted()
}
}
syncer.markSyncReady(syncId);
syncGroup.markSyncReady();
```
```
 No newline at end of file
Loading