gecko-dev/gfx/ipc/GPUProcessManager.cpp
Nika Layzell c3974e5fe9 Bug 1440207 - Part 3a: Migrate GeckoChildProcessHost callers to use ChildProcessArgs, r=ipc-reviewers,necko-reviewers,media-playback-reviewers,aosmond,mccr8
This patch implements the majority of the public interface for the new IPC
handle passing design.

The rough design is an expansion of `geckoargs` to allow passing
`UniqueFileHandle` arguments to child processes. This replaces the existing
extra options array to make the list of files explicit.

This currently just replaces things which were already passed this way on the
command line from outside of GeckoChildProcessHost. Note that this does not
migrate callers which were not already passing file handles using geckoargs,
and does not implement the actual OS-level support for passing arguments this
way.

Differential Revision: https://phabricator.services.mozilla.com/D221371
2024-10-01 22:21:47 +00:00

1762 lines
59 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "GPUProcessManager.h"
#include "gfxConfig.h"
#include "gfxPlatform.h"
#include "GPUProcessHost.h"
#include "GPUProcessListener.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/MemoryReportingProcess.h"
#include "mozilla/Preferences.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_layers.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/RemoteDecoderManagerChild.h"
#include "mozilla/RemoteDecoderManagerParent.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/GPUChild.h"
#include "mozilla/gfx/GPUParent.h"
#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/ProcessChild.h"
#include "mozilla/layers/APZCTreeManagerChild.h"
#include "mozilla/layers/APZInputBridgeChild.h"
#include "mozilla/layers/CompositorBridgeChild.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/CompositorManagerChild.h"
#include "mozilla/layers/CompositorManagerParent.h"
#include "mozilla/layers/CompositorOptions.h"
#include "mozilla/layers/ImageBridgeChild.h"
#include "mozilla/layers/ImageBridgeParent.h"
#include "mozilla/layers/InProcessCompositorSession.h"
#include "mozilla/layers/LayerTreeOwnerTracker.h"
#include "mozilla/layers/RemoteCompositorSession.h"
#include "mozilla/webrender/RenderThread.h"
#include "mozilla/widget/PlatformWidgetTypes.h"
#include "nsAppRunner.h"
#include "mozilla/widget/CompositorWidget.h"
#ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING
# include "mozilla/widget/CompositorWidgetChild.h"
#endif
#include "nsBaseWidget.h"
#include "nsContentUtils.h"
#include "VRManagerChild.h"
#include "VRManagerParent.h"
#include "VsyncBridgeChild.h"
#include "VsyncIOThreadHolder.h"
#include "VsyncSource.h"
#include "nsExceptionHandler.h"
#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)
#if defined(XP_WIN)
# include "gfxWindowsPlatform.h"
#endif
namespace mozilla {
namespace gfx {
using namespace mozilla::layers;
static StaticAutoPtr<GPUProcessManager> sSingleton;
GPUProcessManager* GPUProcessManager::Get() { return sSingleton; }
void GPUProcessManager::Initialize() {
MOZ_ASSERT(XRE_IsParentProcess());
sSingleton = new GPUProcessManager();
}
void GPUProcessManager::Shutdown() { sSingleton = nullptr; }
GPUProcessManager::GPUProcessManager()
: mTaskFactory(this),
mNextNamespace(0),
mIdNamespace(0),
mResourceId(0),
mUnstableProcessAttempts(0),
mTotalProcessAttempts(0),
mDeviceResetCount(0),
mAppInForeground(true),
mProcess(nullptr),
mProcessToken(0),
mProcessStable(true),
mGPUChild(nullptr) {
MOZ_COUNT_CTOR(GPUProcessManager);
mIdNamespace = AllocateNamespace();
mDeviceResetLastTime = TimeStamp::Now();
LayerTreeOwnerTracker::Initialize();
CompositorBridgeParent::InitializeStatics();
}
GPUProcessManager::~GPUProcessManager() {
MOZ_COUNT_DTOR(GPUProcessManager);
LayerTreeOwnerTracker::Shutdown();
// The GPU process should have already been shut down.
MOZ_ASSERT(!mProcess && !mGPUChild);
// We should have already removed observers.
MOZ_ASSERT(!mObserver);
}
NS_IMPL_ISUPPORTS(GPUProcessManager::Observer, nsIObserver);
GPUProcessManager::Observer::Observer(GPUProcessManager* aManager)
: mManager(aManager) {}
NS_IMETHODIMP
GPUProcessManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
mManager->OnXPCOMShutdown();
} else if (!strcmp(aTopic, "nsPref:changed")) {
mManager->OnPreferenceChange(aData);
} else if (!strcmp(aTopic, "application-foreground")) {
mManager->mAppInForeground = true;
if (!mManager->mProcess && gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
Unused << mManager->LaunchGPUProcess();
}
} else if (!strcmp(aTopic, "application-background")) {
mManager->mAppInForeground = false;
} else if (!strcmp(aTopic, "screen-information-changed")) {
mManager->ScreenInformationChanged();
}
return NS_OK;
}
GPUProcessManager::BatteryObserver::BatteryObserver(GPUProcessManager* aManager)
: mManager(aManager) {
hal::RegisterBatteryObserver(this);
}
void GPUProcessManager::BatteryObserver::Notify(
const hal::BatteryInformation& aBatteryInfo) {
mManager->NotifyBatteryInfo(aBatteryInfo);
}
void GPUProcessManager::BatteryObserver::ShutDown() {
hal::UnregisterBatteryObserver(this);
}
GPUProcessManager::BatteryObserver::~BatteryObserver() {}
void GPUProcessManager::OnXPCOMShutdown() {
if (mObserver) {
nsContentUtils::UnregisterShutdownObserver(mObserver);
Preferences::RemoveObserver(mObserver, "");
nsCOMPtr<nsIObserverService> obsServ = services::GetObserverService();
if (obsServ) {
obsServ->RemoveObserver(mObserver, "application-foreground");
obsServ->RemoveObserver(mObserver, "application-background");
obsServ->RemoveObserver(mObserver, "screen-information-changed");
}
mObserver = nullptr;
}
CleanShutdown();
}
void GPUProcessManager::OnPreferenceChange(const char16_t* aData) {
// We know prefs are ASCII here.
NS_LossyConvertUTF16toASCII strData(aData);
mozilla::dom::Pref pref(strData, /* isLocked */ false,
/* isSanitized */ false, Nothing(), Nothing());
Preferences::GetPreference(&pref, GeckoProcessType_GPU,
/* remoteType */ ""_ns);
if (!!mGPUChild) {
MOZ_ASSERT(mQueuedPrefs.IsEmpty());
mGPUChild->SendPreferenceUpdate(pref);
} else if (IsGPUProcessLaunching()) {
mQueuedPrefs.AppendElement(pref);
}
}
void GPUProcessManager::ScreenInformationChanged() {
#if defined(XP_WIN)
if (!!mGPUChild) {
mGPUChild->SendScreenInformationChanged();
}
#endif
}
void GPUProcessManager::NotifyBatteryInfo(
const hal::BatteryInformation& aBatteryInfo) {
if (mGPUChild) {
mGPUChild->SendNotifyBatteryInfo(aBatteryInfo);
}
}
void GPUProcessManager::ResetProcessStable() {
mTotalProcessAttempts++;
mProcessStable = false;
mProcessAttemptLastTime = TimeStamp::Now();
}
bool GPUProcessManager::IsProcessStable(const TimeStamp& aNow) {
#ifdef MOZ_WIDGET_ANDROID
// On Android if the process is lost whilst in the background it was probably
// killed by the OS, and it may never have had a chance to have been declared
// stable prior to being killed. We don't want this happening repeatedly to
// result in the GPU process being disabled, so treat any process lost whilst
// in the background as stable.
if (!mAppInForeground) {
return true;
}
#endif
if (mTotalProcessAttempts > 0) {
auto delta = (int32_t)(aNow - mProcessAttemptLastTime).ToMilliseconds();
if (delta < StaticPrefs::layers_gpu_process_stable_min_uptime_ms()) {
return false;
}
}
return mProcessStable;
}
bool GPUProcessManager::LaunchGPUProcess() {
if (mProcess) {
return true;
}
if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdown)) {
return false;
}
// Start listening for pref changes so we can
// forward them to the process once it is running.
if (!mObserver) {
mObserver = new Observer(this);
nsContentUtils::RegisterShutdownObserver(mObserver);
Preferences::AddStrongObserver(mObserver, "");
nsCOMPtr<nsIObserverService> obsServ = services::GetObserverService();
if (obsServ) {
obsServ->AddObserver(mObserver, "application-foreground", false);
obsServ->AddObserver(mObserver, "application-background", false);
obsServ->AddObserver(mObserver, "screen-information-changed", false);
}
}
// Start the Vsync I/O thread so can use it as soon as the process launches.
EnsureVsyncIOThread();
mTotalProcessAttempts++;
mozilla::glean::gpu_process::total_launch_attempts.Set(mTotalProcessAttempts);
mProcessAttemptLastTime = TimeStamp::Now();
mProcessStable = false;
geckoargs::ChildProcessArgs extraArgs;
ipc::ProcessChild::AddPlatformBuildID(extraArgs);
// The subprocess is launched asynchronously, so we wait for a callback to
// acquire the IPDL actor.
mProcess = new GPUProcessHost(this);
if (!mProcess->Launch(std::move(extraArgs))) {
DisableGPUProcess("Failed to launch GPU process");
}
return true;
}
bool GPUProcessManager::IsGPUProcessLaunching() {
MOZ_ASSERT(NS_IsMainThread());
return !!mProcess && !mGPUChild;
}
void GPUProcessManager::DisableGPUProcess(const char* aMessage) {
MaybeDisableGPUProcess(aMessage, /* aAllowRestart */ false);
}
bool GPUProcessManager::MaybeDisableGPUProcess(const char* aMessage,
bool aAllowRestart) {
if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
return true;
}
if (!aAllowRestart) {
gfxConfig::SetFailed(Feature::GPU_PROCESS, FeatureStatus::Failed, aMessage);
gfxVars::SetGPUProcessEnabled(false);
}
bool wantRestart;
if (mLastError) {
wantRestart =
FallbackFromAcceleration(mLastError.value(), mLastErrorMsg.ref());
mLastError.reset();
mLastErrorMsg.reset();
} else {
wantRestart = gfxPlatform::FallbackFromAcceleration(
FeatureStatus::Unavailable, aMessage,
"FEATURE_FAILURE_GPU_PROCESS_ERROR"_ns);
}
if (aAllowRestart && wantRestart) {
// The fallback method can make use of the GPU process.
return false;
}
if (aAllowRestart) {
gfxConfig::SetFailed(Feature::GPU_PROCESS, FeatureStatus::Failed, aMessage);
gfxVars::SetGPUProcessEnabled(false);
}
MOZ_ASSERT(!gfxConfig::IsEnabled(Feature::GPU_PROCESS));
gfxCriticalNote << aMessage;
gfxPlatform::DisableGPUProcess();
mozilla::glean::gpu_process::feature_status.Set(
gfxConfig::GetFeature(Feature::GPU_PROCESS)
.GetStatusAndFailureIdString());
mozilla::glean::gpu_process::crash_fallbacks.Get("disabled"_ns).Add(1);
DestroyProcess();
ShutdownVsyncIOThread();
// Now the stability state is based upon the in process compositor session.
ResetProcessStable();
// We may have been in the middle of guaranteeing our various services are
// available when one failed. Some callers may fallback to using the same
// process equivalent, and we need to make sure those services are setup
// correctly. We cannot re-enter DisableGPUProcess from this call because we
// know that it is disabled in the config above.
DebugOnly<bool> ready = EnsureProtocolsReady();
MOZ_ASSERT_IF(!ready,
AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdown));
// If we disable the GPU process during reinitialization after a previous
// crash, then we need to tell the content processes again, because they
// need to rebind to the UI process.
HandleProcessLost();
return true;
}
nsresult GPUProcessManager::EnsureGPUReady(
bool aRetryAfterFallback /* = true */) {
MOZ_ASSERT(NS_IsMainThread());
// We only wait to fail with NS_ERROR_ILLEGAL_DURING_SHUTDOWN if we would
// cause a state change or if we are in the middle of relaunching the GPU
// process.
bool inShutdown = AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdown);
while (true) {
// Launch the GPU process if it is enabled but hasn't been (re-)launched
// yet.
if (!mProcess && gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
if (NS_WARN_IF(inShutdown)) {
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}
if (!LaunchGPUProcess()) {
return NS_ERROR_FAILURE;
}
}
if (mProcess && !mProcess->IsConnected()) {
if (NS_WARN_IF(inShutdown)) {
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}
if (!mProcess->WaitForLaunch()) {
// If this fails, we should have fired OnProcessLaunchComplete and
// removed the process.
MOZ_ASSERT(!mProcess && !mGPUChild);
return NS_ERROR_FAILURE;
}
}
// The only scenario this should be possible is if we raced with the
// initialization, which failed, and has already decided to disable the GPU
// process.
if (!mGPUChild) {
MOZ_DIAGNOSTIC_ASSERT(!gfxConfig::IsEnabled(Feature::GPU_PROCESS));
break;
}
if (mGPUChild->EnsureGPUReady()) {
return NS_OK;
}
// If the initialization above fails, we likely have a GPU process teardown
// waiting in our message queue (or will soon). If the fallback wants us to
// give up on the GPU process, we will exit the loop.
if (MaybeDisableGPUProcess("Failed to initialize GPU process",
/* aAllowRestart */ true)) {
MOZ_DIAGNOSTIC_ASSERT(!gfxConfig::IsEnabled(Feature::GPU_PROCESS));
break;
}
// Otherwise HandleProcessLost will explicitly teardown the process and
// prevent any pending events from triggering our fallback logic again, and
// we will retry with a different configuration.
MOZ_DIAGNOSTIC_ASSERT(gfxConfig::IsEnabled(Feature::GPU_PROCESS));
OnBlockingProcessUnexpectedShutdown();
// Some callers may need to reconfigure if we fellback.
if (!aRetryAfterFallback) {
return NS_ERROR_NOT_AVAILABLE;
}
}
// This is the first time we are trying to use the in-process compositor.
if (mTotalProcessAttempts == 0) {
if (NS_WARN_IF(inShutdown)) {
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}
ResetProcessStable();
}
return NS_ERROR_FAILURE;
}
bool GPUProcessManager::EnsureProtocolsReady() {
return EnsureCompositorManagerChild() && EnsureImageBridgeChild() &&
EnsureVRManager();
}
bool GPUProcessManager::EnsureCompositorManagerChild() {
nsresult rv = EnsureGPUReady();
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return false;
}
if (CompositorManagerChild::IsInitialized(mProcessToken)) {
return true;
}
if (NS_FAILED(rv)) {
CompositorManagerChild::InitSameProcess(AllocateNamespace(), mProcessToken);
return true;
}
ipc::Endpoint<PCompositorManagerParent> parentPipe;
ipc::Endpoint<PCompositorManagerChild> childPipe;
rv = PCompositorManager::CreateEndpoints(mGPUChild->OtherEndpointProcInfo(),
ipc::EndpointProcInfo::Current(),
&parentPipe, &childPipe);
if (NS_FAILED(rv)) {
DisableGPUProcess("Failed to create PCompositorManager endpoints");
return true;
}
uint32_t cmNamespace = AllocateNamespace();
mGPUChild->SendInitCompositorManager(std::move(parentPipe), cmNamespace);
CompositorManagerChild::Init(std::move(childPipe), cmNamespace,
mProcessToken);
return true;
}
bool GPUProcessManager::EnsureImageBridgeChild() {
if (ImageBridgeChild::GetSingleton()) {
return true;
}
nsresult rv = EnsureGPUReady();
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return false;
}
if (NS_FAILED(rv)) {
ImageBridgeChild::InitSameProcess(AllocateNamespace());
return true;
}
ipc::Endpoint<PImageBridgeParent> parentPipe;
ipc::Endpoint<PImageBridgeChild> childPipe;
rv = PImageBridge::CreateEndpoints(mGPUChild->OtherEndpointProcInfo(),
ipc::EndpointProcInfo::Current(),
&parentPipe, &childPipe);
if (NS_FAILED(rv)) {
DisableGPUProcess("Failed to create PImageBridge endpoints");
return true;
}
mGPUChild->SendInitImageBridge(std::move(parentPipe));
ImageBridgeChild::InitWithGPUProcess(std::move(childPipe),
AllocateNamespace());
return true;
}
bool GPUProcessManager::EnsureVRManager() {
if (VRManagerChild::IsCreated()) {
return true;
}
nsresult rv = EnsureGPUReady();
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return false;
}
if (NS_FAILED(rv)) {
VRManagerChild::InitSameProcess();
return true;
}
ipc::Endpoint<PVRManagerParent> parentPipe;
ipc::Endpoint<PVRManagerChild> childPipe;
rv = PVRManager::CreateEndpoints(mGPUChild->OtherEndpointProcInfo(),
ipc::EndpointProcInfo::Current(),
&parentPipe, &childPipe);
if (NS_FAILED(rv)) {
DisableGPUProcess("Failed to create PVRManager endpoints");
return true;
}
mGPUChild->SendInitVRManager(std::move(parentPipe));
VRManagerChild::InitWithGPUProcess(std::move(childPipe));
return true;
}
#if defined(MOZ_WIDGET_ANDROID)
already_AddRefed<UiCompositorControllerChild>
GPUProcessManager::CreateUiCompositorController(nsBaseWidget* aWidget,
const LayersId aId) {
RefPtr<UiCompositorControllerChild> result;
nsresult rv = EnsureGPUReady();
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return nullptr;
}
if (NS_FAILED(rv)) {
result = UiCompositorControllerChild::CreateForSameProcess(aId, aWidget);
} else {
ipc::Endpoint<PUiCompositorControllerParent> parentPipe;
ipc::Endpoint<PUiCompositorControllerChild> childPipe;
rv = PUiCompositorController::CreateEndpoints(
mGPUChild->OtherEndpointProcInfo(), ipc::EndpointProcInfo::Current(),
&parentPipe, &childPipe);
if (NS_FAILED(rv)) {
DisableGPUProcess("Failed to create PUiCompositorController endpoints");
return nullptr;
}
mGPUChild->SendInitUiCompositorController(aId, std::move(parentPipe));
result = UiCompositorControllerChild::CreateForGPUProcess(
mProcessToken, std::move(childPipe), aWidget);
if (result) {
result->SetCompositorSurfaceManager(
mProcess->GetCompositorSurfaceManager());
}
}
return result.forget();
}
#endif // defined(MOZ_WIDGET_ANDROID)
void GPUProcessManager::OnProcessLaunchComplete(GPUProcessHost* aHost) {
MOZ_ASSERT(mProcess && mProcess == aHost);
if (!mProcess->IsConnected()) {
DisableGPUProcess("Failed to connect GPU process");
return;
}
mGPUChild = mProcess->GetActor();
mProcessToken = mProcess->GetProcessToken();
#if defined(XP_WIN)
if (mAppInForeground) {
SetProcessIsForeground();
}
#endif
ipc::Endpoint<PVsyncBridgeParent> vsyncParent;
ipc::Endpoint<PVsyncBridgeChild> vsyncChild;
nsresult rv = PVsyncBridge::CreateEndpoints(
mGPUChild->OtherEndpointProcInfo(), ipc::EndpointProcInfo::Current(),
&vsyncParent, &vsyncChild);
if (NS_FAILED(rv)) {
DisableGPUProcess("Failed to create PVsyncBridge endpoints");
return;
}
mVsyncBridge = VsyncBridgeChild::Create(mVsyncIOThread, mProcessToken,
std::move(vsyncChild));
mGPUChild->SendInitVsyncBridge(std::move(vsyncParent));
MOZ_ASSERT(!mBatteryObserver);
mBatteryObserver = new BatteryObserver(this);
// Flush any pref updates that happened during launch and weren't
// included in the blobs set up in LaunchGPUProcess.
for (const mozilla::dom::Pref& pref : mQueuedPrefs) {
Unused << NS_WARN_IF(!mGPUChild->SendPreferenceUpdate(pref));
}
mQueuedPrefs.Clear();
CrashReporter::RecordAnnotationCString(
CrashReporter::Annotation::GPUProcessStatus, "Running");
CrashReporter::RecordAnnotationU32(
CrashReporter::Annotation::GPUProcessLaunchCount, mTotalProcessAttempts);
ReinitializeRendering();
}
void GPUProcessManager::OnProcessDeclaredStable() { mProcessStable = true; }
static bool ShouldLimitDeviceResets(uint32_t count, int32_t deltaMilliseconds) {
// We decide to limit by comparing the amount of resets that have happened
// and time since the last reset to two prefs.
int32_t timeLimit = StaticPrefs::gfx_device_reset_threshold_ms_AtStartup();
int32_t countLimit = StaticPrefs::gfx_device_reset_limit_AtStartup();
bool hasTimeLimit = timeLimit >= 0;
bool hasCountLimit = countLimit >= 0;
bool triggeredTime = deltaMilliseconds < timeLimit;
bool triggeredCount = count > (uint32_t)countLimit;
// If we have both prefs set then it needs to trigger both limits,
// otherwise we only test the pref that is set or none
if (hasTimeLimit && hasCountLimit) {
return triggeredTime && triggeredCount;
} else if (hasTimeLimit) {
return triggeredTime;
} else if (hasCountLimit) {
return triggeredCount;
}
return false;
}
void GPUProcessManager::ResetCompositors() {
// Note: this will recreate devices in addition to recreating compositors.
// This isn't optimal, but this is only used on linux where acceleration
// isn't enabled by default, and this way we don't need a new code path.
SimulateDeviceReset();
}
void GPUProcessManager::SimulateDeviceReset() {
// Make sure we rebuild environment and configuration for accelerated
// features.
gfxPlatform::GetPlatform()->CompositorUpdated();
if (mProcess) {
if (mGPUChild) {
mGPUChild->SendSimulateDeviceReset();
}
} else {
wr::RenderThread::Get()->SimulateDeviceReset();
}
}
bool GPUProcessManager::FallbackFromAcceleration(wr::WebRenderError aError,
const nsCString& aMsg) {
if (aError == wr::WebRenderError::INITIALIZE) {
return gfxPlatform::FallbackFromAcceleration(
gfx::FeatureStatus::Unavailable, "WebRender initialization failed",
aMsg);
} else if (aError == wr::WebRenderError::MAKE_CURRENT) {
return gfxPlatform::FallbackFromAcceleration(
gfx::FeatureStatus::Unavailable,
"Failed to make render context current",
"FEATURE_FAILURE_WEBRENDER_MAKE_CURRENT"_ns);
} else if (aError == wr::WebRenderError::RENDER) {
return gfxPlatform::FallbackFromAcceleration(
gfx::FeatureStatus::Unavailable, "Failed to render WebRender",
"FEATURE_FAILURE_WEBRENDER_RENDER"_ns);
} else if (aError == wr::WebRenderError::NEW_SURFACE) {
// If we cannot create a new Surface even in the final fallback
// configuration then force a crash.
return gfxPlatform::FallbackFromAcceleration(
gfx::FeatureStatus::Unavailable, "Failed to create new surface",
"FEATURE_FAILURE_WEBRENDER_NEW_SURFACE"_ns,
/* aCrashAfterFinalFallback */ true);
} else if (aError == wr::WebRenderError::BEGIN_DRAW) {
return gfxPlatform::FallbackFromAcceleration(
gfx::FeatureStatus::Unavailable, "BeginDraw() failed",
"FEATURE_FAILURE_WEBRENDER_BEGIN_DRAW"_ns);
} else if (aError == wr::WebRenderError::EXCESSIVE_RESETS) {
return gfxPlatform::FallbackFromAcceleration(
gfx::FeatureStatus::Unavailable, "Device resets exceeded threshold",
"FEATURE_FAILURE_WEBRENDER_EXCESSIVE_RESETS"_ns);
} else {
MOZ_ASSERT_UNREACHABLE("Invalid value");
return gfxPlatform::FallbackFromAcceleration(
gfx::FeatureStatus::Unavailable, "Unhandled failure reason",
"FEATURE_FAILURE_WEBRENDER_UNHANDLED"_ns);
}
}
bool GPUProcessManager::DisableWebRenderConfig(wr::WebRenderError aError,
const nsCString& aMsg) {
// If we have a stable compositor process, this may just be due to an OOM or
// bad driver state. In that case, we should consider restarting the GPU
// process, or simulating a device reset to teardown the compositors to
// hopefully alleviate the situation.
if (IsProcessStable(TimeStamp::Now())) {
if (mProcess) {
mProcess->KillProcess(/* aGenerateMinidump */ false);
} else {
SimulateDeviceReset();
}
mLastError = Some(aError);
mLastErrorMsg = Some(aMsg);
return false;
}
mLastError.reset();
mLastErrorMsg.reset();
// Disable WebRender
bool wantRestart = FallbackFromAcceleration(aError, aMsg);
gfxVars::SetUseWebRenderDCompVideoHwOverlayWin(false);
gfxVars::SetUseWebRenderDCompVideoSwOverlayWin(false);
// If we still have the GPU process, and we fallback to a new configuration
// that prefers to have the GPU process, reset the counter. Because we
// updated the gfxVars, we want to flag the GPUChild to wait for the update
// to be processed before creating new compositor sessions, otherwise we risk
// them being out of sync with the content/parent processes.
if (wantRestart && mProcess && mGPUChild) {
mUnstableProcessAttempts = 1;
mGPUChild->MarkWaitForVarUpdate();
}
return true;
}
void GPUProcessManager::DisableWebRender(wr::WebRenderError aError,
const nsCString& aMsg) {
if (DisableWebRenderConfig(aError, aMsg)) {
if (mProcess) {
DestroyRemoteCompositorSessions();
} else {
DestroyInProcessCompositorSessions();
}
NotifyListenersOnCompositeDeviceReset();
}
}
void GPUProcessManager::NotifyWebRenderError(wr::WebRenderError aError) {
gfxCriticalNote << "Handling webrender error " << (unsigned int)aError;
#ifdef XP_WIN
if (aError == wr::WebRenderError::VIDEO_OVERLAY) {
gfxVars::SetUseWebRenderDCompVideoHwOverlayWin(false);
gfxVars::SetUseWebRenderDCompVideoSwOverlayWin(false);
return;
}
if (aError == wr::WebRenderError::VIDEO_HW_OVERLAY) {
gfxVars::SetUseWebRenderDCompVideoHwOverlayWin(false);
return;
}
if (aError == wr::WebRenderError::VIDEO_SW_OVERLAY) {
gfxVars::SetUseWebRenderDCompVideoSwOverlayWin(false);
return;
}
#else
if (aError == wr::WebRenderError::VIDEO_OVERLAY ||
aError == wr::WebRenderError::VIDEO_HW_OVERLAY ||
aError == wr::WebRenderError::VIDEO_SW_OVERLAY) {
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
return;
}
#endif
DisableWebRender(aError, nsCString());
}
/* static */
void GPUProcessManager::RecordDeviceReset(DeviceResetReason aReason) {
if (aReason != DeviceResetReason::FORCED_RESET) {
Telemetry::Accumulate(Telemetry::DEVICE_RESET_REASON, uint32_t(aReason));
}
CrashReporter::RecordAnnotationU32(
CrashReporter::Annotation::DeviceResetReason,
static_cast<uint32_t>(aReason));
}
/* static */
void GPUProcessManager::NotifyDeviceReset(DeviceResetReason aReason,
DeviceResetDetectPlace aPlace) {
if (XRE_IsGPUProcess()) {
if (!GPUParent::GetSingleton()) {
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
return;
}
// End up to GPUProcessManager::OnRemoteProcessDeviceReset()
GPUParent::GetSingleton()->NotifyDeviceReset(aReason, aPlace);
} else {
if (!GPUProcessManager::Get()) {
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
return;
}
if (NS_IsMainThread()) {
GPUProcessManager::Get()->OnInProcessDeviceReset(aReason, aPlace);
} else {
NS_DispatchToMainThread(NS_NewRunnableFunction(
"gfx::GPUProcessManager::OnInProcessDeviceReset",
[aReason, aPlace]() -> void {
gfx::GPUProcessManager::Get()->OnInProcessDeviceReset(aReason,
aPlace);
}));
}
}
}
bool GPUProcessManager::OnDeviceReset(bool aTrackThreshold) {
// Ignore resets for thresholding if requested.
if (!aTrackThreshold) {
return false;
}
// Detect whether the device is resetting too quickly or too much
// indicating that we should give up and use software
mDeviceResetCount++;
auto newTime = TimeStamp::Now();
auto delta = (int32_t)(newTime - mDeviceResetLastTime).ToMilliseconds();
mDeviceResetLastTime = newTime;
// Returns true if we should disable acceleration due to the reset.
return ShouldLimitDeviceResets(mDeviceResetCount, delta);
}
void GPUProcessManager::OnInProcessDeviceReset(DeviceResetReason aReason,
DeviceResetDetectPlace aPlace) {
gfxCriticalNote << "Detect DeviceReset " << aReason << " " << aPlace
<< " in Parent process";
bool guilty;
switch (aReason) {
case DeviceResetReason::HUNG:
case DeviceResetReason::RESET:
case DeviceResetReason::INVALID_CALL:
guilty = true;
break;
default:
guilty = false;
break;
}
if (OnDeviceReset(guilty)) {
gfxCriticalNoteOnce << "In-process device reset threshold exceeded";
#ifdef MOZ_WIDGET_GTK
// FIXME(aosmond): Should we disable WebRender on other platforms?
DisableWebRenderConfig(wr::WebRenderError::EXCESSIVE_RESETS, nsCString());
#endif
}
#ifdef XP_WIN
// Ensure device reset handling before re-creating in process sessions.
// Normally nsWindow::OnPaint() already handled it.
gfxWindowsPlatform::GetPlatform()->HandleDeviceReset();
#endif
DestroyInProcessCompositorSessions();
NotifyListenersOnCompositeDeviceReset();
}
void GPUProcessManager::OnRemoteProcessDeviceReset(
GPUProcessHost* aHost, const DeviceResetReason& aReason,
const DeviceResetDetectPlace& aPlace) {
gfxCriticalNote << "Detect DeviceReset " << aReason << " " << aPlace
<< " in GPU process";
if (OnDeviceReset(/* aTrackThreshold */ true) &&
!DisableWebRenderConfig(wr::WebRenderError::EXCESSIVE_RESETS,
nsCString())) {
return;
}
DestroyRemoteCompositorSessions();
NotifyListenersOnCompositeDeviceReset();
}
void GPUProcessManager::NotifyListenersOnCompositeDeviceReset() {
for (const auto& listener : mListeners) {
listener->OnCompositorDeviceReset();
}
}
void GPUProcessManager::OnBlockingProcessUnexpectedShutdown() {
if (mProcess) {
CompositorManagerChild::OnGPUProcessLost(mProcess->GetProcessToken());
}
DestroyProcess(/* aUnexpectedShutdown */ true);
mUnstableProcessAttempts = 0;
HandleProcessLost();
}
void GPUProcessManager::OnProcessUnexpectedShutdown(GPUProcessHost* aHost) {
MOZ_ASSERT(mProcess && mProcess == aHost);
if (StaticPrefs::layers_gpu_process_crash_also_crashes_browser()) {
MOZ_CRASH("GPU process crashed and pref is set to crash the browser.");
}
CompositorManagerChild::OnGPUProcessLost(aHost->GetProcessToken());
DestroyProcess(/* aUnexpectedShutdown */ true);
// If the process didn't live long enough, increment our unstable attempts
// counter so that we don't end up in a restart loop. If the process did live
// long enough, reset the counter so that we don't disable the process too
// eagerly.
if (IsProcessStable(TimeStamp::Now())) {
mUnstableProcessAttempts = 0;
} else {
mUnstableProcessAttempts++;
mozilla::glean::gpu_process::unstable_launch_attempts.Set(
mUnstableProcessAttempts);
}
if (mUnstableProcessAttempts >
uint32_t(StaticPrefs::layers_gpu_process_max_restarts())) {
char disableMessage[64];
SprintfLiteral(disableMessage, "GPU process disabled after %d attempts",
mTotalProcessAttempts);
if (!MaybeDisableGPUProcess(disableMessage, /* aAllowRestart */ true)) {
// Fallback wants the GPU process. Reset our counter.
MOZ_DIAGNOSTIC_ASSERT(gfxConfig::IsEnabled(Feature::GPU_PROCESS));
mUnstableProcessAttempts = 0;
HandleProcessLost();
} else {
MOZ_DIAGNOSTIC_ASSERT(!gfxConfig::IsEnabled(Feature::GPU_PROCESS));
}
} else if (mUnstableProcessAttempts >
uint32_t(StaticPrefs::
layers_gpu_process_max_restarts_with_decoder()) &&
mDecodeVideoOnGpuProcess) {
mDecodeVideoOnGpuProcess = false;
mozilla::glean::gpu_process::crash_fallbacks.Get("decoding_disabled"_ns)
.Add(1);
HandleProcessLost();
} else {
mozilla::glean::gpu_process::crash_fallbacks.Get("none"_ns).Add(1);
HandleProcessLost();
}
}
void GPUProcessManager::HandleProcessLost() {
MOZ_ASSERT(NS_IsMainThread());
// The shutdown and restart sequence for the GPU process is as follows:
//
// (1) The GPU process dies. IPDL will enqueue an ActorDestroy message on
// each channel owning a bridge to the GPU process, on the thread owning
// that channel.
//
// (2) The first channel to process its ActorDestroy message will post a
// message to the main thread to call NotifyRemoteActorDestroyed on the
// GPUProcessManager, which calls OnProcessUnexpectedShutdown if it has
// not handled shutdown for this process yet. OnProcessUnexpectedShutdown
// is responsible for tearing down the old process and deciding whether
// or not to disable the GPU process. It then calls this function,
// HandleProcessLost.
//
// (3) We then notify each widget that its session with the compositor is now
// invalid. The widget is responsible for destroying its layer manager
// and CompositorBridgeChild. Note that at this stage, not all actors may
// have received ActorDestroy yet. CompositorBridgeChild may attempt to
// send messages, and if this happens, it will probably report a
// MsgDropped error. This is okay.
//
// (4) At this point, the UI process has a clean slate: no layers should
// exist for the old compositor. We may make a decision on whether or not
// to re-launch the GPU process. Or, on Android if the app is in the
// background we may decide to wait until it comes to the foreground
// before re-launching.
//
// (5) When we do decide to re-launch, or continue without a GPU process, we
// notify each ContentParent of the lost connection. It will request new
// endpoints from the GPUProcessManager and forward them to its
// ContentChild. The parent-side of these endpoints may come from the
// compositor thread of the UI process, or the compositor thread of the
// GPU process. However, no actual compositors should exist yet.
//
// (6) Each ContentChild will receive new endpoints. It will destroy its
// Compositor/ImageBridgeChild singletons and recreate them, as well
// as invalidate all retained layers.
//
// (7) In addition, each ContentChild will ask each of its BrowserChildren
// to re-request association with the compositor for the window
// owning the tab. The sequence of calls looks like:
// (a) [CONTENT] ContentChild::RecvReinitRendering
// (b) [CONTENT] BrowserChild::ReinitRendering
// (c) [CONTENT] BrowserChild::SendEnsureLayersConnected
// (d) [UI] BrowserParent::RecvEnsureLayersConnected
// (e) [UI] RemoteLayerTreeOwner::EnsureLayersConnected
// (f) [UI] CompositorBridgeChild::SendNotifyChildRecreated
//
// Note that at step (e), RemoteLayerTreeOwner will call
// GetWindowRenderer on the nsIWidget owning the tab. This step ensures
// that a compositor exists for the window. If we decided to launch a new
// GPU Process, at this point we block until the process has launched and
// we're able to create a new window compositor. Otherwise, if
// compositing is now in-process, this will simply create a new
// CompositorBridgeParent in the UI process. If there are multiple tabs
// in the same window, additional tabs will simply return the already-
// established compositor.
//
// Finally, this step serves one other crucial function: tabs must be
// associated with a window compositor or else they can't forward
// layer transactions. So this step both ensures that a compositor
// exists, and that the tab can forward layers.
//
// (8) Last, if the window had no remote tabs, step (7) will not have
// applied, and the window will not have a new compositor just yet. The
// next refresh tick and paint will ensure that one exists, again via
// nsIWidget::GetWindowRenderer. On Android, we called
// nsIWidgetListener::RequestRepaint back in step (3) to ensure this
// tick occurs, but on other platforms this is not necessary.
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.
if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
#ifdef MOZ_WIDGET_ANDROID
if (mAppInForeground) {
#else
{
#endif
Unused << LaunchGPUProcess();
}
} else {
// If the GPU process is disabled we can reinitialize rendering immediately.
// This will be handled in OnProcessLaunchComplete() if the GPU process is
// enabled.
ReinitializeRendering();
}
}
void GPUProcessManager::ReinitializeRendering() {
// Notify content. This will ensure that each content process re-establishes
// a connection to the compositor thread (whether it's in-process or in a
// newly launched GPU process).
for (const auto& listener : mListeners) {
listener->OnCompositorUnexpectedShutdown();
}
// Notify any observers that the compositor has been reinitialized,
// eg the ZoomConstraintsClients for parent process documents.
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
if (observerService) {
observerService->NotifyObservers(nullptr, "compositor-reinitialized",
nullptr);
}
}
void GPUProcessManager::DestroyRemoteCompositorSessions() {
// Build a list of sessions to notify, since notification might delete
// entries from the list.
nsTArray<RefPtr<RemoteCompositorSession>> sessions;
for (auto& session : mRemoteSessions) {
sessions.AppendElement(session);
}
// Notify each widget that we have lost the GPU process. This will ensure
// that each widget destroys its layer manager and CompositorBridgeChild.
for (const auto& session : sessions) {
session->NotifySessionLost();
}
}
void GPUProcessManager::DestroyInProcessCompositorSessions() {
// Build a list of sessions to notify, since notification might delete
// entries from the list.
nsTArray<RefPtr<InProcessCompositorSession>> sessions;
for (auto& session : mInProcessSessions) {
sessions.AppendElement(session);
}
// Notify each widget that we have lost the GPU process. This will ensure
// that each widget destroys its layer manager and CompositorBridgeChild.
for (const auto& session : sessions) {
session->NotifySessionLost();
}
// Ensure our stablility state is reset so that we don't necessarily crash
// right away on some WebRender errors.
CompositorBridgeParent::ResetStable();
ResetProcessStable();
}
void GPUProcessManager::NotifyRemoteActorDestroyed(
const uint64_t& aProcessToken) {
if (!NS_IsMainThread()) {
RefPtr<Runnable> task = mTaskFactory.NewRunnableMethod(
&GPUProcessManager::NotifyRemoteActorDestroyed, aProcessToken);
NS_DispatchToMainThread(task.forget());
return;
}
if (mProcessToken != aProcessToken) {
// This token is for an older process; we can safely ignore it.
return;
}
// One of the bridged top-level actors for the GPU process has been
// prematurely terminated, and we're receiving a notification. This
// can happen if the ActorDestroy for a bridged protocol fires
// before the ActorDestroy for PGPUChild.
OnProcessUnexpectedShutdown(mProcess);
}
void GPUProcessManager::CleanShutdown() {
DestroyProcess();
mVsyncIOThread = nullptr;
}
void GPUProcessManager::KillProcess(bool aGenerateMinidump) {
if (!NS_IsMainThread()) {
RefPtr<Runnable> task = mTaskFactory.NewRunnableMethod(
&GPUProcessManager::KillProcess, aGenerateMinidump);
NS_DispatchToMainThread(task.forget());
return;
}
if (!mProcess) {
return;
}
mProcess->KillProcess(aGenerateMinidump);
}
void GPUProcessManager::CrashProcess() {
if (!mProcess) {
return;
}
mProcess->CrashProcess();
}
void GPUProcessManager::DestroyProcess(bool aUnexpectedShutdown) {
if (!mProcess) {
return;
}
mProcess->Shutdown(aUnexpectedShutdown);
mProcessToken = 0;
mProcess = nullptr;
mGPUChild = nullptr;
mQueuedPrefs.Clear();
if (mVsyncBridge) {
mVsyncBridge->Close();
mVsyncBridge = nullptr;
}
if (mBatteryObserver) {
mBatteryObserver->ShutDown();
mBatteryObserver = nullptr;
}
CrashReporter::RecordAnnotationCString(
CrashReporter::Annotation::GPUProcessStatus, "Destroyed");
}
already_AddRefed<CompositorSession> GPUProcessManager::CreateTopLevelCompositor(
nsBaseWidget* aWidget, WebRenderLayerManager* aLayerManager,
CSSToLayoutDeviceScale aScale, const CompositorOptions& aOptions,
bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize,
uint64_t aInnerWindowId, bool* aRetryOut) {
MOZ_ASSERT(aRetryOut);
LayersId layerTreeId = AllocateLayerTreeId();
RefPtr<CompositorSession> session;
nsresult rv = EnsureGPUReady(/* aRetryAfterFallback */ false);
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
*aRetryOut = false;
return nullptr;
}
// If we used fallback, then retry creating the compositor sessions because
// our configuration may have changed.
if (rv == NS_ERROR_NOT_AVAILABLE) {
*aRetryOut = true;
return nullptr;
}
if (!EnsureProtocolsReady()) {
*aRetryOut = false;
return nullptr;
}
if (NS_SUCCEEDED(rv)) {
session = CreateRemoteSession(aWidget, aLayerManager, layerTreeId, aScale,
aOptions, aUseExternalSurfaceSize,
aSurfaceSize, aInnerWindowId);
if (NS_WARN_IF(!session)) {
if (!MaybeDisableGPUProcess("Failed to create remote compositor",
/* aAllowRestart */ true)) {
// Fallback wants the GPU process. Reset our counter.
MOZ_DIAGNOSTIC_ASSERT(gfxConfig::IsEnabled(Feature::GPU_PROCESS));
OnBlockingProcessUnexpectedShutdown();
} else {
MOZ_DIAGNOSTIC_ASSERT(!gfxConfig::IsEnabled(Feature::GPU_PROCESS));
}
*aRetryOut = true;
return nullptr;
}
} else {
session = InProcessCompositorSession::Create(
aWidget, aLayerManager, layerTreeId, aScale, aOptions,
aUseExternalSurfaceSize, aSurfaceSize, AllocateNamespace(),
aInnerWindowId);
}
#if defined(MOZ_WIDGET_ANDROID)
if (session) {
// Nothing to do if controller gets a nullptr
RefPtr<UiCompositorControllerChild> controller =
CreateUiCompositorController(aWidget, session->RootLayerTreeId());
MOZ_ASSERT(controller);
session->SetUiCompositorControllerChild(controller);
}
#endif // defined(MOZ_WIDGET_ANDROID)
*aRetryOut = false;
return session.forget();
}
RefPtr<CompositorSession> GPUProcessManager::CreateRemoteSession(
nsBaseWidget* aWidget, WebRenderLayerManager* aLayerManager,
const LayersId& aRootLayerTreeId, CSSToLayoutDeviceScale aScale,
const CompositorOptions& aOptions, bool aUseExternalSurfaceSize,
const gfx::IntSize& aSurfaceSize, uint64_t aInnerWindowId) {
#ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING
widget::CompositorWidgetInitData initData;
aWidget->GetCompositorWidgetInitData(&initData);
RefPtr<CompositorBridgeChild> child =
CompositorManagerChild::CreateWidgetCompositorBridge(
mProcessToken, aLayerManager, AllocateNamespace(), aScale, aOptions,
aUseExternalSurfaceSize, aSurfaceSize, aInnerWindowId);
if (!child) {
gfxCriticalNote << "Failed to create CompositorBridgeChild";
return nullptr;
}
RefPtr<CompositorVsyncDispatcher> dispatcher =
aWidget->GetCompositorVsyncDispatcher();
RefPtr<widget::CompositorWidgetVsyncObserver> observer =
new widget::CompositorWidgetVsyncObserver(mVsyncBridge, aRootLayerTreeId);
widget::CompositorWidgetChild* widget =
new widget::CompositorWidgetChild(dispatcher, observer, initData);
if (!child->SendPCompositorWidgetConstructor(widget, initData)) {
return nullptr;
}
if (!widget->Initialize()) {
return nullptr;
}
if (!child->SendInitialize(aRootLayerTreeId)) {
return nullptr;
}
RefPtr<APZCTreeManagerChild> apz = nullptr;
if (aOptions.UseAPZ()) {
PAPZCTreeManagerChild* papz =
child->SendPAPZCTreeManagerConstructor(LayersId{0});
if (!papz) {
return nullptr;
}
apz = static_cast<APZCTreeManagerChild*>(papz);
ipc::Endpoint<PAPZInputBridgeParent> parentPipe;
ipc::Endpoint<PAPZInputBridgeChild> childPipe;
nsresult rv = PAPZInputBridge::CreateEndpoints(
mGPUChild->OtherEndpointProcInfo(), ipc::EndpointProcInfo::Current(),
&parentPipe, &childPipe);
if (NS_FAILED(rv)) {
return nullptr;
}
mGPUChild->SendInitAPZInputBridge(aRootLayerTreeId, std::move(parentPipe));
RefPtr<APZInputBridgeChild> inputBridge =
APZInputBridgeChild::Create(mProcessToken, std::move(childPipe));
if (!inputBridge) {
return nullptr;
}
apz->SetInputBridge(inputBridge);
}
return new RemoteCompositorSession(aWidget, child, widget, apz,
aRootLayerTreeId);
#else
gfxCriticalNote << "Platform does not support out-of-process compositing";
return nullptr;
#endif
}
bool GPUProcessManager::CreateContentBridges(
ipc::EndpointProcInfo aOtherProcess,
ipc::Endpoint<PCompositorManagerChild>* aOutCompositor,
ipc::Endpoint<PImageBridgeChild>* aOutImageBridge,
ipc::Endpoint<PVRManagerChild>* aOutVRBridge,
ipc::Endpoint<PRemoteDecoderManagerChild>* aOutVideoManager,
dom::ContentParentId aChildId, nsTArray<uint32_t>* aNamespaces) {
const uint32_t cmNamespace = AllocateNamespace();
if (!CreateContentCompositorManager(aOtherProcess, aChildId, cmNamespace,
aOutCompositor) ||
!CreateContentImageBridge(aOtherProcess, aChildId, aOutImageBridge) ||
!CreateContentVRManager(aOtherProcess, aChildId, aOutVRBridge)) {
return false;
}
// VideoDeocderManager is only supported in the GPU process, so we allow this
// to be fallible.
CreateContentRemoteDecoderManager(aOtherProcess, aChildId, aOutVideoManager);
// Allocates 3 namespaces(for CompositorManagerChild, CompositorBridgeChild
// and ImageBridgeChild)
aNamespaces->AppendElement(cmNamespace);
aNamespaces->AppendElement(AllocateNamespace());
aNamespaces->AppendElement(AllocateNamespace());
return true;
}
bool GPUProcessManager::CreateContentCompositorManager(
ipc::EndpointProcInfo aOtherProcess, dom::ContentParentId aChildId,
uint32_t aNamespace, ipc::Endpoint<PCompositorManagerChild>* aOutEndpoint) {
ipc::Endpoint<PCompositorManagerParent> parentPipe;
ipc::Endpoint<PCompositorManagerChild> childPipe;
nsresult rv = EnsureGPUReady();
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return false;
}
ipc::EndpointProcInfo parentInfo = NS_SUCCEEDED(rv)
? mGPUChild->OtherEndpointProcInfo()
: ipc::EndpointProcInfo::Current();
rv = PCompositorManager::CreateEndpoints(parentInfo, aOtherProcess,
&parentPipe, &childPipe);
if (NS_FAILED(rv)) {
gfxCriticalNote << "Could not create content compositor manager: "
<< hexa(int(rv));
return false;
}
if (mGPUChild) {
mGPUChild->SendNewContentCompositorManager(std::move(parentPipe), aChildId,
aNamespace);
} else if (!CompositorManagerParent::Create(std::move(parentPipe), aChildId,
aNamespace,
/* aIsRoot */ false)) {
return false;
}
*aOutEndpoint = std::move(childPipe);
return true;
}
bool GPUProcessManager::CreateContentImageBridge(
ipc::EndpointProcInfo aOtherProcess, dom::ContentParentId aChildId,
ipc::Endpoint<PImageBridgeChild>* aOutEndpoint) {
if (!EnsureImageBridgeChild()) {
return false;
}
nsresult rv = EnsureGPUReady();
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return false;
}
ipc::EndpointProcInfo parentInfo = NS_SUCCEEDED(rv)
? mGPUChild->OtherEndpointProcInfo()
: ipc::EndpointProcInfo::Current();
ipc::Endpoint<PImageBridgeParent> parentPipe;
ipc::Endpoint<PImageBridgeChild> childPipe;
rv = PImageBridge::CreateEndpoints(parentInfo, aOtherProcess, &parentPipe,
&childPipe);
if (NS_FAILED(rv)) {
gfxCriticalNote << "Could not create content compositor bridge: "
<< hexa(int(rv));
return false;
}
if (mGPUChild) {
mGPUChild->SendNewContentImageBridge(std::move(parentPipe), aChildId);
} else {
if (!ImageBridgeParent::CreateForContent(std::move(parentPipe), aChildId)) {
return false;
}
}
*aOutEndpoint = std::move(childPipe);
return true;
}
base::ProcessId GPUProcessManager::GPUProcessPid() {
base::ProcessId gpuPid =
mGPUChild ? mGPUChild->OtherPid() : base::kInvalidProcessId;
return gpuPid;
}
ipc::EndpointProcInfo GPUProcessManager::GPUEndpointProcInfo() {
return mGPUChild ? mGPUChild->OtherEndpointProcInfo()
: ipc::EndpointProcInfo::Invalid();
}
bool GPUProcessManager::CreateContentVRManager(
ipc::EndpointProcInfo aOtherProcess, dom::ContentParentId aChildId,
ipc::Endpoint<PVRManagerChild>* aOutEndpoint) {
if (NS_WARN_IF(!EnsureVRManager())) {
return false;
}
nsresult rv = EnsureGPUReady();
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return false;
}
ipc::EndpointProcInfo parentInfo = NS_SUCCEEDED(rv)
? mGPUChild->OtherEndpointProcInfo()
: ipc::EndpointProcInfo::Current();
ipc::Endpoint<PVRManagerParent> parentPipe;
ipc::Endpoint<PVRManagerChild> childPipe;
rv = PVRManager::CreateEndpoints(parentInfo, aOtherProcess, &parentPipe,
&childPipe);
if (NS_FAILED(rv)) {
gfxCriticalNote << "Could not create content compositor bridge: "
<< hexa(int(rv));
return false;
}
if (mGPUChild) {
mGPUChild->SendNewContentVRManager(std::move(parentPipe), aChildId);
} else {
if (!VRManagerParent::CreateForContent(std::move(parentPipe), aChildId)) {
return false;
}
}
*aOutEndpoint = std::move(childPipe);
return true;
}
void GPUProcessManager::CreateContentRemoteDecoderManager(
ipc::EndpointProcInfo aOtherProcess, dom::ContentParentId aChildId,
ipc::Endpoint<PRemoteDecoderManagerChild>* aOutEndpoint) {
nsresult rv = EnsureGPUReady();
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return;
}
if (NS_FAILED(rv) || !StaticPrefs::media_gpu_process_decoder() ||
!mDecodeVideoOnGpuProcess) {
return;
}
ipc::Endpoint<PRemoteDecoderManagerParent> parentPipe;
ipc::Endpoint<PRemoteDecoderManagerChild> childPipe;
rv = PRemoteDecoderManager::CreateEndpoints(
mGPUChild->OtherEndpointProcInfo(), aOtherProcess, &parentPipe,
&childPipe);
if (NS_FAILED(rv)) {
gfxCriticalNote << "Could not create content video decoder: "
<< hexa(int(rv));
return;
}
mGPUChild->SendNewContentRemoteDecoderManager(std::move(parentPipe),
aChildId);
*aOutEndpoint = std::move(childPipe);
}
void GPUProcessManager::InitVideoBridge(
ipc::Endpoint<PVideoBridgeParent>&& aVideoBridge,
layers::VideoBridgeSource aSource) {
nsresult rv = EnsureGPUReady();
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return;
}
if (NS_SUCCEEDED(rv)) {
mGPUChild->SendInitVideoBridge(std::move(aVideoBridge), aSource);
}
}
void GPUProcessManager::MapLayerTreeId(LayersId aLayersId,
base::ProcessId aOwningId) {
nsresult rv = EnsureGPUReady();
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return;
}
if (NS_SUCCEEDED(rv)) {
mGPUChild->SendAddLayerTreeIdMapping(
LayerTreeIdMapping(aLayersId, aOwningId));
}
// Must do this *after* the call to EnsureGPUReady, so that if the
// process is launched as a result then it is initialized without this
// LayersId, meaning it can be successfully mapped.
LayerTreeOwnerTracker::Get()->Map(aLayersId, aOwningId);
}
void GPUProcessManager::UnmapLayerTreeId(LayersId aLayersId,
base::ProcessId aOwningId) {
// Only call EnsureGPUReady() if we have already launched the process, to
// avoid launching a new process unnecesarily. (eg if we are backgrounded)
nsresult rv = mProcess ? EnsureGPUReady() : NS_ERROR_NOT_AVAILABLE;
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return;
}
if (NS_SUCCEEDED(rv)) {
mGPUChild->SendRemoveLayerTreeIdMapping(
LayerTreeIdMapping(aLayersId, aOwningId));
} else if (!mProcess) {
CompositorBridgeParent::DeallocateLayerTreeId(aLayersId);
}
// Must do this *after* the call to EnsureGPUReady, so that if the
// process is launched as a result then it is initialized with this
// LayersId, meaning it can be successfully unmapped.
LayerTreeOwnerTracker::Get()->Unmap(aLayersId, aOwningId);
}
bool GPUProcessManager::IsLayerTreeIdMapped(LayersId aLayersId,
base::ProcessId aRequestingId) {
return LayerTreeOwnerTracker::Get()->IsMapped(aLayersId, aRequestingId);
}
LayersId GPUProcessManager::AllocateLayerTreeId() {
// Allocate tree id by using id namespace.
// By it, tree id does not conflict with external image id and
// async image pipeline id.
MOZ_ASSERT(NS_IsMainThread());
++mResourceId;
if (mResourceId == UINT32_MAX) {
// Move to next id namespace.
mIdNamespace = AllocateNamespace();
mResourceId = 1;
}
uint64_t layerTreeId = mIdNamespace;
layerTreeId = (layerTreeId << 32) | mResourceId;
return LayersId{layerTreeId};
}
uint32_t GPUProcessManager::AllocateNamespace() {
MOZ_ASSERT(NS_IsMainThread());
return ++mNextNamespace;
}
bool GPUProcessManager::AllocateAndConnectLayerTreeId(
PCompositorBridgeChild* aCompositorBridge, base::ProcessId aOtherPid,
LayersId* aOutLayersId, CompositorOptions* aOutCompositorOptions) {
LayersId layersId = AllocateLayerTreeId();
*aOutLayersId = layersId;
if (!mGPUChild || !aCompositorBridge) {
// If we're not remoting to another process, or there is no compositor,
// then we'll send at most one message. In this case we can just keep
// the old behavior of making sure the mapping occurs, and maybe sending
// a creation notification.
MapLayerTreeId(layersId, aOtherPid);
if (!aCompositorBridge) {
return false;
}
return aCompositorBridge->SendNotifyChildCreated(layersId,
aOutCompositorOptions);
}
// Use the combined message path.
LayerTreeOwnerTracker::Get()->Map(layersId, aOtherPid);
return aCompositorBridge->SendMapAndNotifyChildCreated(layersId, aOtherPid,
aOutCompositorOptions);
}
void GPUProcessManager::EnsureVsyncIOThread() {
if (mVsyncIOThread) {
return;
}
mVsyncIOThread = new VsyncIOThreadHolder();
MOZ_RELEASE_ASSERT(mVsyncIOThread->Start());
}
void GPUProcessManager::ShutdownVsyncIOThread() { mVsyncIOThread = nullptr; }
void GPUProcessManager::RegisterRemoteProcessSession(
RemoteCompositorSession* aSession) {
mRemoteSessions.AppendElement(aSession);
}
void GPUProcessManager::UnregisterRemoteProcessSession(
RemoteCompositorSession* aSession) {
mRemoteSessions.RemoveElement(aSession);
}
void GPUProcessManager::RegisterInProcessSession(
InProcessCompositorSession* aSession) {
mInProcessSessions.AppendElement(aSession);
}
void GPUProcessManager::UnregisterInProcessSession(
InProcessCompositorSession* aSession) {
mInProcessSessions.RemoveElement(aSession);
}
void GPUProcessManager::AddListener(GPUProcessListener* aListener) {
mListeners.AppendElement(aListener);
}
void GPUProcessManager::RemoveListener(GPUProcessListener* aListener) {
mListeners.RemoveElement(aListener);
}
bool GPUProcessManager::NotifyGpuObservers(const char* aTopic) {
if (NS_FAILED(EnsureGPUReady())) {
return false;
}
nsCString topic(aTopic);
mGPUChild->SendNotifyGpuObservers(topic);
return true;
}
class GPUMemoryReporter : public MemoryReportingProcess {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GPUMemoryReporter, override)
bool IsAlive() const override {
if (GPUProcessManager* gpm = GPUProcessManager::Get()) {
return !!gpm->GetGPUChild();
}
return false;
}
bool SendRequestMemoryReport(
const uint32_t& aGeneration, const bool& aAnonymize,
const bool& aMinimizeMemoryUsage,
const Maybe<ipc::FileDescriptor>& aDMDFile) override {
GPUChild* child = GetChild();
if (!child) {
return false;
}
return child->SendRequestMemoryReport(aGeneration, aAnonymize,
aMinimizeMemoryUsage, aDMDFile);
}
int32_t Pid() const override {
if (GPUChild* child = GetChild()) {
return (int32_t)child->OtherPid();
}
return 0;
}
private:
GPUChild* GetChild() const {
if (GPUProcessManager* gpm = GPUProcessManager::Get()) {
if (GPUChild* child = gpm->GetGPUChild()) {
return child;
}
}
return nullptr;
}
protected:
~GPUMemoryReporter() = default;
};
RefPtr<MemoryReportingProcess> GPUProcessManager::GetProcessMemoryReporter() {
// Ensure mProcess is non-null before calling EnsureGPUReady, to avoid
// launching the process if it has not already been launched.
if (!mProcess || NS_FAILED(EnsureGPUReady())) {
return nullptr;
}
return new GPUMemoryReporter();
}
void GPUProcessManager::SetAppInForeground(bool aInForeground) {
if (mAppInForeground == aInForeground) {
return;
}
mAppInForeground = aInForeground;
#if defined(XP_WIN)
SetProcessIsForeground();
#endif
}
#if defined(XP_WIN)
void GPUProcessManager::SetProcessIsForeground() {
NTSTATUS WINAPI NtSetInformationProcess(
IN HANDLE process_handle, IN ULONG info_class,
IN PVOID process_information, IN ULONG information_length);
constexpr unsigned int NtProcessInformationForeground = 25;
static bool alreadyInitialized = false;
static decltype(NtSetInformationProcess)* setInformationProcess = nullptr;
if (!alreadyInitialized) {
alreadyInitialized = true;
nsModuleHandle module(LoadLibrary(L"ntdll.dll"));
if (module) {
setInformationProcess =
(decltype(NtSetInformationProcess)*)GetProcAddress(
module, "NtSetInformationProcess");
}
}
if (MOZ_UNLIKELY(!setInformationProcess)) {
return;
}
unsigned pid = GPUProcessPid();
if (pid <= 0) {
return;
}
// Using the handle from mProcess->GetChildProcessHandle() fails;
// the PROCESS_SET_INFORMATION permission is probably missing.
nsAutoHandle processHandle(
::OpenProcess(PROCESS_SET_INFORMATION, FALSE, pid));
if (!processHandle) {
return;
}
BOOLEAN foreground = mAppInForeground;
setInformationProcess(processHandle, NtProcessInformationForeground,
(PVOID)&foreground, sizeof(foreground));
}
#endif
RefPtr<PGPUChild::TestTriggerMetricsPromise>
GPUProcessManager::TestTriggerMetrics() {
if (!NS_WARN_IF(!mGPUChild)) {
return mGPUChild->SendTestTriggerMetrics();
}
return PGPUChild::TestTriggerMetricsPromise::CreateAndReject(
ipc::ResponseRejectReason::SendError, __func__);
}
} // namespace gfx
} // namespace mozilla