Bug 1722450 [Wayland] Pause compositor when screen scale changes and content is not updated yet, r=sotaro

We need to track window compositor state in various cases, when compositor is paused after window creation,
when compositor is paused for hidden window (EGLSurface is missing) and paused compositor when layout is not updated yet
and drawing will lead to incorrect / obsoleted window content.

In this patch we track compositor state by WindowCompositorState atomic enum (because it can be accessed from Compositor thread)
and two private methods (PauseCompositorHiddenWindow and ResumeCompositorHiddenWindow) and two public ones (PauseCompositor() and ResumeCompositor).

Private interface is used by nsWindow when EGLSurface is missing so we can't render to window - it's initial compositor pause and
pause when window is hidden.

Public interface is used by nsWindow and WebRender compositor to disable/enable rendering when window content is updated.
When compositor is disabled by public interface it will be enabled automatically after a timeout (1s) or when content layout is updated.

Changes in the patch:

- Implement GtkCompositorWidget::RemoteLayoutSizeUpdated() to notify GtkCompositorWidget widget about layout size update in content process.
- Track nsWindow compositor state by WindowCompositorState
- Implement nsWindow::PauseCompositorHiddenWindow() and ResumeCompositorHiddenWindow() to handle internal nsWindow states.
- Implement nsWindow::PauseCompositor() and nsWindow::ResumeCompositor() to allow compositor pause/resume during content layout updates.
- Use timeout to resume compositor paused by public interface as a fallback when content is not updated or it takes too long.

Differential Revision: https://phabricator.services.mozilla.com/D121650
This commit is contained in:
stransky 2021-08-11 07:48:53 +00:00
Родитель 39d89466c3
Коммит e9207013f4
5 изменённых файлов: 197 добавлений и 56 удалений

Просмотреть файл

@ -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<uint8_t> 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);

Просмотреть файл

@ -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<uintptr_t>(mWidget);
}

Просмотреть файл

@ -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; }

Просмотреть файл

@ -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<nsWindow> 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<nsWindow> 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<nsWindow> 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<nsWindow*>(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<nsIRunnable> 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;

Просмотреть файл

@ -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;