diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceControlManager.java | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceControlManager.java')
-rw-r--r-- | mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceControlManager.java | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceControlManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceControlManager.java new file mode 100644 index 0000000000..e02ab98952 --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceControlManager.java @@ -0,0 +1,105 @@ +/* -*- 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); + } + + // Removes an existing parent SurfaceControl and its corresponding child from the manager. This + // can be used when we require the next call to getChildSurface() for the specified parent to + // create a new child rather than return the existing one. + @RequiresApi(api = Build.VERSION_CODES.Q) + @WrapForJNI(exceptionMode = "abort") + public synchronized void removeSurface(final SurfaceControl parent) { + final SurfaceControl child = mChildSurfaceControls.remove(parent); + if (child != null) { + child.release(); + } + } + + // 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(); + } +} |