Skip to content

Commit

Permalink
Bug 1767128 - Rework and re-enable SurfaceControl rendering path on A…
Browse files Browse the repository at this point in the history
…ndroid. r=agi,gfx-reviewers,aosmond

In bug 1762424 we introduced a rendering path on Android using the
SurfaceControl API, in order to work around a bug preventing recovery
from a GPU process crash. However, the initial implementation caused
this bug: repeatedly sending the same SurfaceControl objects over AIDL
to the GPU process resulted in them being leaked, eventually causing
severe display issues. Not only were we duplicating the SurfaceControl
for each widget, but each time a widget was resized too.

This patch reworks our usage of the SurfaceControl API to avoid ever
having to send them cross-process. Instead, we create a single child
SurfaceControl object for each SurfaceControl that is attached to a
widget. (Typically there will be a single one shared between all
widgets, changing only when the app is paused and resumed, which is
much fewer than one per widget per resize.)

In the parent process we obtain the Surfaces that will be rendered in
to from the child SurfaceControls, and only send those Surfaces to the
GPU process. Thankfully unlike SurfaceControls, sending Surfaces
cross-process does not cause leaks. When the GPU process dies we
simply destroy all of the child SurfaceControls, and recreate them
again on-demand.

Differential Revision: https://phabricator.services.mozilla.com/D147437
  • Loading branch information
jamienicol committed May 31, 2022
1 parent f950793 commit 2cf59fe
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 77 deletions.
5 changes: 5 additions & 0 deletions gfx/ipc/GPUProcessManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include "nsPrintfCString.h"

#if defined(MOZ_WIDGET_ANDROID)
# include "mozilla/java/SurfaceControlManagerWrappers.h"
# include "mozilla/widget/AndroidUiThread.h"
# include "mozilla/layers/UiCompositorControllerChild.h"
#endif // defined(MOZ_WIDGET_ANDROID)
Expand Down Expand Up @@ -825,6 +826,10 @@ void GPUProcessManager::HandleProcessLost() {

DestroyRemoteCompositorSessions();

#ifdef MOZ_WIDGET_ANDROID
java::SurfaceControlManager::GetInstance()->OnGpuProcessLoss();
#endif

// Re-launch the process if immediately if the GPU process is still enabled.
// Except on Android if the app is in the background, where we want to wait
// until the app is in the foreground again.
Expand Down
7 changes: 3 additions & 4 deletions gfx/layers/ipc/UiCompositorControllerChild.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,16 +332,15 @@ void UiCompositorControllerChild::SetCompositorSurfaceManager(
};

void UiCompositorControllerChild::OnCompositorSurfaceChanged(
int32_t aWidgetId, java::sdk::Surface::Param aSurface,
java::sdk::SurfaceControl::Param aSurfaceControl) {
int32_t aWidgetId, java::sdk::Surface::Param aSurface) {
// If mCompositorSurfaceManager is not set then there is no GPU process and
// we do not need to do anything.
if (mCompositorSurfaceManager == nullptr) {
return;
}

nsresult result = mCompositorSurfaceManager->OnSurfaceChanged(
aWidgetId, aSurface, aSurfaceControl);
nsresult result =
mCompositorSurfaceManager->OnSurfaceChanged(aWidgetId, aSurface);

// If our remote binder has died then notify the GPU process manager.
if (NS_FAILED(result)) {
Expand Down
5 changes: 2 additions & 3 deletions gfx/layers/ipc/UiCompositorControllerChild.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,8 @@ class UiCompositorControllerChild final
// Note that this function does not actually use the PUiCompositorController
// IPDL protocol, and instead uses Android's binder IPC mechanism via
// mCompositorSurfaceManager. It can be called from any thread.
void OnCompositorSurfaceChanged(
int32_t aWidgetId, java::sdk::Surface::Param aSurface,
java::sdk::SurfaceControl::Param aSurfaceControl);
void OnCompositorSurfaceChanged(int32_t aWidgetId,
java::sdk::Surface::Param aSurface);
#endif

protected:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
package org.mozilla.gecko.gfx;

import android.view.Surface;
import android.view.SurfaceControl;

interface ICompositorSurfaceManager {
void onSurfaceChanged(int widgetId, in Surface surface, in SurfaceControl surfaceControl);
void onSurfaceChanged(int widgetId, in Surface surface);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import android.os.RemoteException;
import android.view.Surface;
import android.view.SurfaceControl;
import org.mozilla.gecko.annotation.WrapForJNI;

public final class CompositorSurfaceManager {
Expand All @@ -20,9 +19,8 @@ public CompositorSurfaceManager(final ICompositorSurfaceManager aManager) {
}

@WrapForJNI(exceptionMode = "nsresult")
public synchronized void onSurfaceChanged(
final int widgetId, final Surface surface, final SurfaceControl surfaceControl)
public synchronized void onSurfaceChanged(final int widgetId, final Surface surface)
throws RemoteException {
mManager.onSurfaceChanged(widgetId, surface, surfaceControl);
mManager.onSurfaceChanged(widgetId, surface);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko.gfx;

import android.os.Build;
import android.view.Surface;
import android.view.SurfaceControl;
import androidx.annotation.RequiresApi;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import org.mozilla.gecko.annotation.WrapForJNI;

// A helper class that creates Surfaces from SurfaceControl objects, for the widget to render in to.
// Unlike the Surfaces provided to the widget directly from the application, these are suitable for
// use in the GPU process as well as the main process.
//
// The reason we must not render directly in to the Surface provided by the application from the GPU
// process is because of a bug on Android versions 12 and later: when the GPU process dies the
// Surface is not detached from the dead process' EGL surface, and any subsequent attempts to
// attach another EGL surface to the Surface will fail.
//
// The application is therefore required to provide the SurfaceControl object to a GeckoDisplay
// whenever rendering in to a SurfaceView. The widget will then obtain a Surface from that
// SurfaceControl using getChildSurface(). Internally, this creates another SurfaceControl as a
// child of the provided SurfaceControl, then creates the Surface from that child. If the GPU
// process dies we are able to simply destroy and recreate the child SurfaceControl objects, thereby
// avoiding the bug.
public class SurfaceControlManager {
private static final String LOGTAG = "SurfaceControlManager";

private static final SurfaceControlManager sInstance = new SurfaceControlManager();

private WeakHashMap<SurfaceControl, SurfaceControl> mChildSurfaceControls = new WeakHashMap<>();

@WrapForJNI
public static SurfaceControlManager getInstance() {
return sInstance;
}

// Returns a Surface of the requested size that will be composited in to the specified
// SurfaceControl.
@RequiresApi(api = Build.VERSION_CODES.Q)
@WrapForJNI(exceptionMode = "abort")
public synchronized Surface getChildSurface(
final SurfaceControl parent, final int width, final int height) {
SurfaceControl child = mChildSurfaceControls.get(parent);
if (child == null) {
// We must periodically check if any of the SurfaceControls we are managing have been
// destroyed, as we are unable to directly listen to their SurfaceViews' surfaceDestroyed
// callbacks, and they may not be attached to any compositor when they are destroyed meaning
// we cannot perform cleanup in response to the compositor being paused.
// Doing so here, when we encounter a new SurfaceControl instance, is a reasonable guess as to
// when a previous instance may have been released.
final Iterator<Map.Entry<SurfaceControl, SurfaceControl>> it =
mChildSurfaceControls.entrySet().iterator();
while (it.hasNext()) {
final Map.Entry<SurfaceControl, SurfaceControl> entry = it.next();
if (!entry.getKey().isValid()) {
it.remove();
}
}

child = new SurfaceControl.Builder().setParent(parent).setName("GeckoSurface").build();
mChildSurfaceControls.put(parent, child);
}

new SurfaceControl.Transaction()
.setVisibility(child, true)
.setBufferSize(child, width, height)
.apply();

return new Surface(child);
}

// Must be called whenever the GPU process has died. This destroys all the child SurfaceControls
// that have been created, meaning subsequent calls to getChildSurface() will create new ones.
@RequiresApi(api = Build.VERSION_CODES.Q)
@WrapForJNI(exceptionMode = "abort")
public synchronized void onGpuProcessLoss() {
for (final SurfaceControl child : mChildSurfaceControls.values()) {
// We could reparent the child SurfaceControl to null here to immediately remove it from the
// tree. However, this will result in a black screen while we wait for the new compositor to
// be created. It's preferable for the user to see the old content instead, so simply call
// release().
child.release();
}
mChildSurfaceControls.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import android.os.Binder;
import android.util.SparseArray;
import android.view.Surface;
import android.view.SurfaceControl;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.gfx.ICompositorSurfaceManager;
import org.mozilla.gecko.gfx.ISurfaceAllocator;
Expand Down Expand Up @@ -46,16 +45,9 @@ private static synchronized RemoteCompositorSurfaceManager getInstance() {
}

private final SparseArray<Surface> mSurfaces = new SparseArray<Surface>();
private final SparseArray<SurfaceControl> mSurfaceControls = new SparseArray<SurfaceControl>();

@Override
public synchronized void onSurfaceChanged(
final int widgetId, final Surface surface, final SurfaceControl surfaceControl) {
if (surfaceControl != null) {
mSurfaceControls.put(widgetId, surfaceControl);
} else {
mSurfaceControls.remove(widgetId);
}
public synchronized void onSurfaceChanged(final int widgetId, final Surface surface) {
if (surface != null) {
mSurfaces.put(widgetId, surface);
} else {
Expand All @@ -67,10 +59,5 @@ public synchronized void onSurfaceChanged(
public synchronized Surface getCompositorSurface(final int widgetId) {
return mSurfaces.get(widgetId);
}

@WrapForJNI
public synchronized SurfaceControl getCompositorSurfaceControl(final int widgetId) {
return mSurfaceControls.get(widgetId);
}
}
}
8 changes: 1 addition & 7 deletions widget/android/CompositorWidgetParent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,7 @@ void CompositorWidgetParent::OnCompositorSurfaceChanged() {
java::GeckoServiceGpuProcess::RemoteCompositorSurfaceManager::LocalRef
manager = java::GeckoServiceGpuProcess::RemoteCompositorSurfaceManager::
GetInstance();
java::sdk::SurfaceControl::LocalRef surfaceControl =
manager->GetCompositorSurfaceControl(mWidgetId);
if (surfaceControl) {
mSurface = java::sdk::Surface::FromSurfaceControl(surfaceControl);
} else {
mSurface = manager->GetCompositorSurface(mWidgetId);
}
mSurface = manager->GetCompositorSurface(mWidgetId);
}

} // namespace widget
Expand Down
11 changes: 2 additions & 9 deletions widget/android/InProcessAndroidCompositorWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,8 @@ void InProcessAndroidCompositorWidget::ObserveVsync(VsyncObserver* aObserver) {
nsIWidget* InProcessAndroidCompositorWidget::RealWidget() { return mWindow; }

void InProcessAndroidCompositorWidget::OnCompositorSurfaceChanged() {
java::sdk::SurfaceControl::LocalRef surfaceControl =
java::sdk::SurfaceControl::Ref::From(static_cast<jobject>(
mWindow->GetNativeData(NS_JAVA_SURFACE_CONTROL)));
if (surfaceControl) {
mSurface = java::sdk::Surface::FromSurfaceControl(surfaceControl);
} else {
mSurface = java::sdk::Surface::Ref::From(
static_cast<jobject>(mWindow->GetNativeData(NS_JAVA_SURFACE)));
}
mSurface = java::sdk::Surface::Ref::From(
static_cast<jobject>(mWindow->GetNativeData(NS_JAVA_SURFACE)));
}

void InProcessAndroidCompositorWidget::NotifyClientSizeChanged(
Expand Down
2 changes: 1 addition & 1 deletion widget/android/bindings/SurfaceTexture-classes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
[android.view.Surface = exceptionMode:nsresult]
<init>(Landroid/view/SurfaceControl;)V = stubName:FromSurfaceControl, exceptionMode:abort
[android.view.SurfaceControl = exceptionMode:nsresult]
[android.view.SurfaceControl$Transaction = exceptionMode:abort]
isValid()Z = exceptionMode:abort
1 change: 1 addition & 0 deletions widget/android/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ classes_with_WrapForJNI = [
"SessionTextInput",
"SpeechSynthesisService",
"SurfaceAllocator",
"SurfaceControlManager",
"SurfaceTextureListener",
"TelemetryUtils",
"WebAuthnTokenManager",
Expand Down
Loading

0 comments on commit 2cf59fe

Please sign in to comment.