diff --git a/gfx/layers/wr/WebRenderBridgeParent.cpp b/gfx/layers/wr/WebRenderBridgeParent.cpp index 482fa61dec07..de7fba76bbc2 100644 --- a/gfx/layers/wr/WebRenderBridgeParent.cpp +++ b/gfx/layers/wr/WebRenderBridgeParent.cpp @@ -50,6 +50,9 @@ #ifdef XP_WIN # include "mozilla/widget/WinCompositorWidget.h" #endif +#if defined(MOZ_WIDGET_GTK) +# include "mozilla/widget/GtkCompositorWidget.h" +#endif bool is_in_main_thread() { return NS_IsMainThread(); } @@ -1113,6 +1116,11 @@ bool WebRenderBridgeParent::SetDisplayList( wr::Vec dlCache(std::move(aDLCache)); if (IsRootWebRenderBridgeParent()) { +#ifdef MOZ_WIDGET_GTK + if (mWidget->AsGTK()) { + mWidget->AsGTK()->RemoteLayoutSizeUpdated(aRect); + } +#endif LayoutDeviceIntSize widgetSize = mWidget->GetClientSize(); LayoutDeviceIntRect rect = LayoutDeviceIntRect(LayoutDeviceIntPoint(), widgetSize); diff --git a/widget/gtk/GtkCompositorWidget.cpp b/widget/gtk/GtkCompositorWidget.cpp index e3fc6dc81df4..e73c818a6412 100644 --- a/widget/gtk/GtkCompositorWidget.cpp +++ b/widget/gtk/GtkCompositorWidget.cpp @@ -88,6 +88,22 @@ LayoutDeviceIntSize GtkCompositorWidget::GetClientSize() { return *size; } +void GtkCompositorWidget::RemoteLayoutSizeUpdated( + const LayoutDeviceRect& aSize) { + if (!mWidget || !mWidget->IsWaitingForCompositorResume()) { + return; + } + + // We're waiting for layout to match widget size. + auto clientSize = mClientSize.Lock(); + if (clientSize->width != (int)aSize.width || + clientSize->height != (int)aSize.height) { + return; + } + + mWidget->ResumeCompositorFromCompositorThread(); +} + uintptr_t GtkCompositorWidget::GetWidgetKey() { return reinterpret_cast(mWidget); } diff --git a/widget/gtk/GtkCompositorWidget.h b/widget/gtk/GtkCompositorWidget.h index 580f35ba194d..369266148129 100644 --- a/widget/gtk/GtkCompositorWidget.h +++ b/widget/gtk/GtkCompositorWidget.h @@ -59,6 +59,7 @@ class GtkCompositorWidget : public CompositorWidget, uintptr_t GetWidgetKey() override; LayoutDeviceIntSize GetClientSize() override; + void RemoteLayoutSizeUpdated(const LayoutDeviceRect& aSize); nsIWidget* RealWidget() override; GtkCompositorWidget* AsGTK() override { return this; } diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp index faab483fff3b..70d108cb0502 100644 --- a/widget/gtk/nsWindow.cpp +++ b/widget/gtk/nsWindow.cpp @@ -437,8 +437,8 @@ nsWindow::nsWindow() mGdkWindow(nullptr), mWindowShouldStartDragging(false), mCompositorWidgetDelegate(nullptr), - mNeedsCompositorResume(false), - mCompositorInitiallyPaused(false), + mCompositorState(COMPOSITOR_ENABLED), + mCompositorPauseTimeoutID(0), mHasMappedToplevel(false), mRetryPointerGrab(false), mSizeState(nsSizeMode_Normal), @@ -717,6 +717,11 @@ void nsWindow::Destroy() { } #endif + if (mCompositorPauseTimeoutID) { + g_source_remove(mCompositorPauseTimeoutID); + mCompositorPauseTimeoutID = 0; + } + // It is safe to call DestroyeCompositor several times (here and // in the parent class) since it will take effect only once. // The reason we call it here is because on gtk platforms we need @@ -1314,7 +1319,7 @@ void nsWindow::RemovePopupFromHierarchyList() { void nsWindow::HideWaylandWindow() { LOG(("nsWindow::HideWaylandWindow: [%p]\n", this)); - PauseCompositor(); + PauseCompositorHiddenWindow(); gtk_widget_hide(mShell); } @@ -4722,6 +4727,12 @@ void nsWindow::OnScaleChanged() { return; } + // We pause compositor to avoid rendering of obsoleted remote content which + // produces flickering. + // Re-enable compositor again when remote content is updated or + // timeout happens. + PauseCompositor(); + // Force scale factor recalculation mWindowScaleFactorChanged = true; @@ -5380,12 +5391,16 @@ nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, mContainer = MOZ_CONTAINER(container); #ifdef MOZ_WAYLAND if (GdkIsWaylandDisplay() && mIsAccelerated) { - mCompositorInitiallyPaused = true; + mCompositorState = COMPOSITOR_PAUSED_INITIALLY; RefPtr self(this); moz_container_wayland_add_initial_draw_callback( mContainer, [self]() -> void { - self->mNeedsCompositorResume = true; - self->MaybeResumeCompositor(); + MOZ_LOG(self->IsPopup() ? gWidgetPopupLog : gWidgetLog, + mozilla::LogLevel::Debug, + ("moz_container_wayland initial create " + "ResumeCompositorHiddenWindow()")); + self->mCompositorState = COMPOSITOR_PAUSED_MISSING_EGL_WINDOW; + self->ResumeCompositorHiddenWindow(); }); } #endif @@ -5448,9 +5463,8 @@ nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, mGdkWindow = gtk_widget_get_window(eventWidget); if (GdkIsX11Display() && gfx::gfxVars::UseEGL() && mIsAccelerated) { - mCompositorInitiallyPaused = true; - mNeedsCompositorResume = true; - MaybeResumeCompositor(); + mCompositorState = COMPOSITOR_PAUSED_MISSING_EGL_WINDOW; + ResumeCompositorHiddenWindow(); } if (mIsWaylandPanelWindow) { @@ -5645,12 +5659,11 @@ nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, nullptr); } - LOG(("nsWindow [%p] %s %s\n", (void*)this, - mWindowType == eWindowType_toplevel ? "Toplevel" : "Popup", + LOG(("nsWindow [%p] type %d %s\n", (void*)this, mWindowType, mIsPIPWindow ? "PIP window" : "")); if (mShell) { - LOG(("\tmShell %p mContainer %p mGdkWindow %p 0x%lx\n", mShell, mContainer, - mGdkWindow, + LOG(("\tmShell %p mContainer %p mGdkWindow %p XID 0x%lx\n", mShell, + mContainer, mGdkWindow, GdkIsX11Display() ? gdk_x11_window_get_xid(mGdkWindow) : 0)); } else if (mContainer) { LOG(("\tmContainer %p mGdkWindow %p\n", mContainer, mGdkWindow)); @@ -5884,61 +5897,142 @@ void nsWindow::NativeMoveResize() { } } -void nsWindow::MaybeResumeCompositor() { +void nsWindow::ResumeCompositorHiddenWindow() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); - if (mIsDestroyed || !mNeedsCompositorResume) { + if (mIsDestroyed || mCompositorState == COMPOSITOR_ENABLED || + mCompositorState == COMPOSITOR_PAUSED_INITIALLY) { return; } if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) { + LOG(("nsWindow::ResumeCompositorHiddenWindow [%p]\n", (void*)this)); MOZ_ASSERT(mCompositorWidgetDelegate); if (mCompositorWidgetDelegate) { - mCompositorInitiallyPaused = false; - mNeedsCompositorResume = false; + mCompositorState = COMPOSITOR_ENABLED; remoteRenderer->SendResumeAsync(); } remoteRenderer->SendForcePresent(); } } -void nsWindow::PauseCompositor() { - // Because wl_egl_window is destroyed on moz_container_unmap(), - // the current compositor cannot use it anymore. To avoid crash, - // pause the compositor and destroy EGLSurface & resume the compositor - // and re-create EGLSurface on next expose event. - - // moz_container_wayland_has_egl_window() could not be used here, since - // there is a case that resume compositor is not completed yet. - - // TODO: The compositor backend currently relies on the pause event to work - // around a Gnome specific bug. Remove again once the fix is widely available. - // See bug 1721298 - if ((!mIsAccelerated && !gfx::gfxVars::UseWebRenderCompositor()) || - mIsDestroyed) { +// Because wl_egl_window is destroyed on moz_container_unmap(), +// the current compositor cannot use it anymore. To avoid crash, +// pause the compositor and destroy EGLSurface & resume the compositor +// and re-create EGLSurface on next expose event. +void nsWindow::PauseCompositorHiddenWindow() { + if (!mIsAccelerated || mIsDestroyed || + mCompositorState == COMPOSITOR_PAUSED_INITIALLY) { return; } + LOG(("nsWindow::PauseCompositorHiddenWindow [%p]\n", (void*)this)); + + mCompositorState = COMPOSITOR_PAUSED_MISSING_EGL_WINDOW; + + // Without remote widget / renderer we can't pause compositor. + // So delete LayerManager to avoid EGLSurface access. CompositorBridgeChild* remoteRenderer = GetRemoteRenderer(); - bool needsCompositorPause = - !mNeedsCompositorResume && !!remoteRenderer && mCompositorWidgetDelegate; - if (needsCompositorPause) { - // XXX slow sync IPC - remoteRenderer->SendPause(); -#ifdef MOZ_WAYLAND - if (GdkIsWaylandDisplay()) { - // Re-request initial draw callback - RefPtr self(this); - moz_container_wayland_add_initial_draw_callback( - mContainer, [self]() -> void { - self->mNeedsCompositorResume = true; - self->MaybeResumeCompositor(); - }); - } -#endif - } else { + if (!remoteRenderer || !mCompositorWidgetDelegate) { + LOG((" deleted layer manager")); DestroyLayerManager(); + return; } + + // XXX slow sync IPC + LOG((" paused compositor")); + remoteRenderer->SendPause(); +#ifdef MOZ_WAYLAND + if (GdkIsWaylandDisplay()) { + // Re-request initial draw callback + RefPtr self(this); + moz_container_wayland_add_initial_draw_callback( + mContainer, [self]() -> void { + MOZ_LOG(self->IsPopup() ? gWidgetPopupLog : gWidgetLog, + mozilla::LogLevel::Debug, + ("moz_container_wayland resume callback " + "ResumeCompositorHiddenWindow()")); + self->ResumeCompositorHiddenWindow(); + }); + } +#endif +} + +static int WindowResumeCompositor(void* data) { + nsWindow* window = static_cast(data); + window->ResumeCompositor(); + return true; +} + +// We pause compositor to avoid rendering of obsoleted remote content which +// produces flickering. +// Re-enable compositor again when remote content is updated or +// timeout happens. + +// Define maximal compositor pause when it's paused to avoid flickering, +// in milliseconds. +#define COMPOSITOR_PAUSE_TIMEOUT (1000) + +void nsWindow::PauseCompositor() { + bool pauseCompositor = (mWindowType == eWindowType_toplevel) && + mCompositorState == COMPOSITOR_ENABLED && + mIsAccelerated && mCompositorWidgetDelegate && + !mIsDestroyed; + if (!pauseCompositor) { + return; + } + + LOG(("nsWindow::PauseCompositor() [%p]\n", (void*)this)); + + if (mCompositorPauseTimeoutID) { + g_source_remove(mCompositorPauseTimeoutID); + mCompositorPauseTimeoutID = 0; + } + + CompositorBridgeChild* remoteRenderer = GetRemoteRenderer(); + if (remoteRenderer) { + remoteRenderer->SendPause(); + mCompositorState = COMPOSITOR_PAUSED_FLICKERING; + mCompositorPauseTimeoutID = (int)g_timeout_add( + COMPOSITOR_PAUSE_TIMEOUT, &WindowResumeCompositor, this); + } +} + +bool nsWindow::IsWaitingForCompositorResume() { + return !mIsDestroyed && mCompositorState == COMPOSITOR_PAUSED_FLICKERING; +} + +void nsWindow::ResumeCompositor() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + if (!IsWaitingForCompositorResume()) { + return; + } + + LOG(("nsWindow::ResumeCompositor() [%p]\n", (void*)this)); + + if (mCompositorPauseTimeoutID) { + g_source_remove(mCompositorPauseTimeoutID); + mCompositorPauseTimeoutID = 0; + } + + // We're expected to have mCompositorWidgetDelegate present + // as we don't delete LayerManager (in PauseCompositor()) + // to avoid flickering. + MOZ_RELEASE_ASSERT(mCompositorWidgetDelegate); + + CompositorBridgeChild* remoteRenderer = GetRemoteRenderer(); + if (remoteRenderer) { + mCompositorState = COMPOSITOR_ENABLED; + remoteRenderer->SendResumeAsync(); + remoteRenderer->SendForcePresent(); + } +} + +void nsWindow::ResumeCompositorFromCompositorThread() { + nsCOMPtr event = NewRunnableMethod( + "nsWindow::ResumeCompositor", this, &nsWindow::ResumeCompositor); + NS_DispatchToMainThread(event.forget()); } void nsWindow::WaylandStartVsync() { @@ -8295,13 +8389,16 @@ nsIWidget::WindowRenderer* nsWindow::GetWindowRenderer() { } void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) { + LOG(("nsWindow::SetCompositorWidgetDelegate [%p] %p\n", (void*)this, + delegate)); + if (delegate) { mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate(); MOZ_ASSERT(mCompositorWidgetDelegate, "nsWindow::SetCompositorWidgetDelegate called with a " "non-PlatformCompositorWidgetDelegate"); + ResumeCompositorHiddenWindow(); WaylandStartVsync(); - MaybeResumeCompositor(); } else { WaylandStopVsync(); mCompositorWidgetDelegate = nullptr; diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h index de9f5ce01840..bdde5615eda9 100644 --- a/widget/gtk/nsWindow.h +++ b/widget/gtk/nsWindow.h @@ -407,6 +407,25 @@ class nsWindow final : public nsBaseWidget { }; #endif + typedef enum { + // WebRender compositor is enabled + COMPOSITOR_ENABLED, + // WebRender compositor is paused after window creation. + COMPOSITOR_PAUSED_INITIALLY, + // WebRender compositor is paused because GtkWindow is hidden, + // we can't draw into EGLSurface. + COMPOSITOR_PAUSED_MISSING_EGL_WINDOW, + // WebRender compositor is paused as we're repainting whole window and + // we're waiting for content process to update page content. + COMPOSITOR_PAUSED_FLICKERING + } WindowCompositorState; + + // Pause compositor to avoid rendering artifacts from content process. + void ResumeCompositor(); + void ResumeCompositorFromCompositorThread(); + void PauseCompositor(); + bool IsWaitingForCompositorResume(); + protected: virtual ~nsWindow(); @@ -418,7 +437,7 @@ class nsWindow final : public nsBaseWidget { virtual void RegisterTouchWindow() override; virtual bool CompositorInitiallyPaused() override { #ifdef MOZ_WAYLAND - return mCompositorInitiallyPaused; + return mCompositorState == COMPOSITOR_PAUSED_INITIALLY; #else return false; #endif @@ -471,9 +490,8 @@ class nsWindow final : public nsBaseWidget { void DispatchContextMenuEventFromMouseEvent(uint16_t domButton, GdkEventButton* aEvent); - - void MaybeResumeCompositor(); - void PauseCompositor(); + void ResumeCompositorHiddenWindow(); + void PauseCompositorHiddenWindow(); void WaylandStartVsync(); void WaylandStopVsync(); @@ -518,9 +536,10 @@ class nsWindow final : public nsBaseWidget { GdkWindow* mGdkWindow; bool mWindowShouldStartDragging; PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate; - - bool mNeedsCompositorResume; - bool mCompositorInitiallyPaused; + WindowCompositorState mCompositorState; + // This is used in COMPOSITOR_PAUSED_FLICKERING mode only to resume compositor + // in some reasonable time when page content is not updated. + int mCompositorPauseTimeoutID; uint32_t mHasMappedToplevel : 1, mRetryPointerGrab : 1; nsSizeMode mSizeState;