diff --git a/mfbt/Atomics.h b/mfbt/Atomics.h --- a/mfbt/Atomics.h +++ b/mfbt/Atomics.h @@ -510,10 +510,31 @@ private: Atomic(Atomic& aOther) = delete; }; +/** + * Atomic implementation for double type. + */ +template +class Atomic : protected detail::AtomicBase { + typedef typename detail::AtomicBase Base; + + public: + constexpr Atomic() : Base() {} + explicit constexpr Atomic(double aInit) : Base(aInit) {} + + operator double() const { + return double(Base::Intrinsics::load(Base::mValue)); + } + + double operator=(double aVal) { return Base::operator=(aVal); } + + private: + Atomic(Atomic& aOther) = delete; +}; + // Relax the CPU during a spinlock. It's a good idea to place this in a // spinlock so that the CPU doesn't pipeline the loop otherwise flushing the // pipeline when the loop finally breaks can be expensive. inline void cpu_pause() { #if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || \ diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -17672,10 +17672,16 @@ # occluded. - name: widget.wayland.vsync.keep-firing-at-idle type: bool value: false mirror: always + +# Use SW emulation of frame callback, for testing only +- name: widget.wayland.emulate-frame-callback + type: bool + value: false + mirror: once #endif #ifdef MOZ_WIDGET_GTK # Whether to use DMABuf backend. - name: widget.dmabuf.enabled diff --git a/widget/gtk/WaylandSurface.h b/widget/gtk/WaylandSurface.h new file mode 100644 --- /dev/null +++ b/widget/gtk/WaylandSurface.h @@ -0,0 +1,388 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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/. */ + +#ifndef __MOZ_WAYLAND_SURFACE_H__ +#define __MOZ_WAYLAND_SURFACE_H__ + +#include "nsWaylandDisplay.h" +#include "mozilla/Mutex.h" +#include "mozilla/Atomics.h" +#include "WaylandSurfaceLock.h" + +/* Workaround for bug at wayland-util.h, + * present in wayland-devel < 1.12 + */ +struct wl_surface; +struct wl_subsurface; +struct wl_egl_window; + +class MessageLoop; + +namespace mozilla::widget { + +class WaylandBuffer; + +// WaylandSurface is a wrapper for Wayland rendering target +// which is wl_surface / wl_subsurface. +class WaylandSurface final { + friend WaylandSurfaceLock; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WaylandSurface); + + WaylandSurface(RefPtr aParent, gfx::IntSize aSize); + +#ifdef MOZ_LOGGING + nsAutoCString GetDebugTag() const; + void* GetLoggingWidget() const { return mLoggingWidget; }; + void SetLoggingWidget(void* aWidget) { mLoggingWidget = aWidget; } +#endif + + void InitialFrameCallbackHandler(struct wl_callback* aCallback); + void AddOrFireInitialDrawCallback( + const std::function& aInitialDrawCB); + void ClearInitialDrawCallbacks(); + + void FrameCallbackHandler(struct wl_callback* aCallback, uint32_t aTime, + bool aRoutedFromChildSurface); + + // Run only once at most. If aEmulateFrameCallback is set to true and + // WaylandSurface is mapped, fire aFrameCallbackHandler even without + // frame callback from compositor in sFrameCheckTimeoutMs. + void AddOneTimeFrameCallbackLocked( + const WaylandSurfaceLock& aProofOfLock, + const std::function& aFrameCallbackHandler, + bool aEmulateFrameCallback = false); + + // Run frame callback repeatedly. If aEmulateFrameCallback is set to true and + // WaylandSurface is mapped, fire aFrameCallbackHandler even without + // frame callback from compositor in sFrameCheckTimeoutMs. + void AddPersistentFrameCallbackLocked( + const WaylandSurfaceLock& aProofOfLock, + const std::function& aFrameCallbackHandler, + bool aEmulateFrameCallback = false); + + // Create and resize EGL window. + // GetEGLWindow() takes unscaled window size as we derive size from GdkWindow. + // It's scaled internally by WaylandSurface fractional scale. + wl_egl_window* GetEGLWindow(nsIntSize aUnscaledSize); + // SetEGLWindowSize() takes scaled size - it's called from rendering code + // which uses scaled sizes. + bool SetEGLWindowSize(nsIntSize aScaledSize); + bool HasEGLWindow() { return !!mEGLWindow; } + + bool DoesCommitToParentSurface() { return mCommitToParentSurface; } + + // Read to draw means we got frame callback from parent surface + // where we attached to. + bool IsReadyToDraw() { return mIsReadyToDraw; } + // Mapped means we have all internals created. + bool IsMapped() { return mIsMapped; } + + bool IsOpaqueSurfaceHandlerSetLocked(const WaylandSurfaceLock& aProofOfLock) { + return mIsOpaqueSurfaceHandlerSet; + } + + // Mapped as direct surface of MozContainer + bool MapLocked(const WaylandSurfaceLock& aProofOfLock, GdkWindow* aGdkWindow, + wl_surface* aParentWLSurface, + gfx::IntPoint aSubsurfacePosition, bool aCommitToParent); + // Mapped as child of WaylandSurface (used by layers) + bool MapLocked(const WaylandSurfaceLock& aProofOfLock, GdkWindow* aGdkWindow, + WaylandSurfaceLock* aParentWaylandSurfaceLock, + gfx::IntPoint aSubsurfacePosition); + void Unmap(); + + // Create Viewport to manage surface transformations. + // aFollowsSizeChanges if set, Viewport destination size + // is updated according to buffer size. + bool CreateViewportLocked(const WaylandSurfaceLock& aProofOfLock, + bool aFollowsSizeChanges); + + void AddInitialDrawCallbackLocked( + const WaylandSurfaceLock& aProofOfLock, + const std::function& aInitialDrawCB); + + // Attach WaylandBuffer which shows WaylandBuffer content + // on screen. + bool AttachLocked(const WaylandSurfaceLock& aProofOfLock, + RefPtr aWaylandBuffer); + // Notify WaylandSurface that WaylandBuffer wes released by Wayland compositor + // and can be reused. We remove WaylandBuffer from 'tracked buffers' + // internal list. + void DetachedLocked(const WaylandSurfaceLock& aProofOfLock, + RefPtr aWaylandBuffer); + + // If there's any WaylandBuffer recently attached, detach it. + // It makes the WaylandSurface invisible and it doesn't have any + // content. + void RemoveAttachedBufferLocked(const WaylandSurfaceLock& aProofOfLock); + + // CommitLocked() is needed to call after some of *Locked() method + // to submit the action to Wayland compositor by wl_surface_commit(). + + // It's possible to stack more *Locked() methods + // together and do commit after the last one to do the changes in atomic way. + + // Need of commit is tracked by mSurfaceNeedsCommit flag and + // if it's set, CommitLocked() is called when WaylandSurfaceLock is destroyed + // and WaylandSurface is unlocked. + void CommitLocked(const WaylandSurfaceLock& aProofOfLock, + bool aForceCommit = false, bool aForceDisplayFlush = false); + + // Place this WaylandSurface above aLowerSurface + void PlaceAboveLocked(const WaylandSurfaceLock& aProofOfLock, + WaylandSurfaceLock& aLowerSurfaceLock); + void MoveLocked(const WaylandSurfaceLock& aProofOfLock, + gfx::IntPoint& aPosition); + void SetViewPortSourceRectLocked(const WaylandSurfaceLock& aProofOfLock, + gfx::Rect aRect); + void SetViewPortDestLocked(const WaylandSurfaceLock& aProofOfLock, + gfx::IntSize aDestSize); + void SetTransformFlippedLocked(const WaylandSurfaceLock& aProofOfLock, + bool aFlippedX, bool aFlippedY); + + void SetOpaqueRegion(const gfx::IntRegion& aRegion); + void SetOpaqueRegionLocked(const WaylandSurfaceLock& aProofOfLock, + const gfx::IntRegion& aRegion); + void SetOpaqueLocked(const WaylandSurfaceLock& aProofOfLock); + + bool DisableUserInputLocked(const WaylandSurfaceLock& aProofOfLock); + void InvalidateRegionLocked(const WaylandSurfaceLock& aProofOfLock, + const gfx::IntRegion& aInvalidRegion); + void InvalidateLocked(const WaylandSurfaceLock& aProofOfLock); + + bool EnableFractionalScaleLocked( + const WaylandSurfaceLock& aProofOfLock, + std::function aFractionalScaleCallback, bool aManageViewport); + bool EnableCeiledScaleLocked(const WaylandSurfaceLock& aProofOfLock); + + bool IsFractionalScaleLocked(const WaylandSurfaceLock& aProofOfLock) { + return mScaleType == ScaleType::Disabled; + } + bool IsCeiledScaleLocked(const WaylandSurfaceLock& aProofOfLock) { + return mScaleType == ScaleType::Ceiled; + } + bool IsScaleEnabledLocked(const WaylandSurfaceLock& aProofOfLock) { + return mScaleType != ScaleType::Disabled; + } + + // Returns scale as float point number. If WaylandSurface is not mapped, + // return fractional scale of parent surface. + // Returns sNoScale is we can't get it. + static constexpr const double sNoScale = -1; + double GetScale(); + + // Called when screen ceiled scale changed or set initial scale before we map + // and paint the surface. + void SetCeiledScaleLocked(const WaylandSurfaceLock& aProofOfLock, + int aScreenCeiledScale); + + // Called by wayland compositor when fractional scale is changed. + static void FractionalScaleHandler(void* data, + struct wp_fractional_scale_v1* info, + uint32_t wire_scale); + + static void OpaqueSurfaceHandler(GdkFrameClock* aClock, void* aData); + + // See https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/3111 why we use it. + // If child surface covers whole area of parent surface, + // Tell Gdk that this WaylandSurface may cover whole area of parent + // surface. In such case + bool AddOpaqueSurfaceHandlerLocked(const WaylandSurfaceLock& aProofOfLock, + bool aRegisterCommitHandler = true); + bool RemoveOpaqueSurfaceHandlerLocked(const WaylandSurfaceLock& aProofOfLock); + + GdkWindow* GetGdkWindow() const; + + static bool IsOpaqueRegionEnabled(); + + void SetParentLocked(const WaylandSurfaceLock& aProofOfLock, + RefPtr aParent); + + private: + ~WaylandSurface(); + + bool MapLocked(const WaylandSurfaceLock& aProofOfLock, GdkWindow* aGdkWindow, + wl_surface* aParentWLSurface, + WaylandSurfaceLock* aParentWaylandSurfaceLock, + gfx::IntPoint aSubsurfacePosition, bool aCommitToParent, + bool aSubsurfaceDesync, bool aRegisterAsOpaqueSurface); + + void SetSizeLocked(const WaylandSurfaceLock& aProofOfLock, + gfx::IntSize aSizeScaled, gfx::IntSize aUnscaledSize); + + wl_surface* Lock(WaylandSurfaceLock* aWaylandSurfaceLock); + void Unlock(struct wl_surface** aSurface, + WaylandSurfaceLock* aWaylandSurfaceLock); + void Commit(WaylandSurfaceLock* aProofOfLock, bool aForceCommit, + bool aForceDisplayFlush); + + bool CheckAndRemoveWaylandBuffer(WaylandBuffer* aWaylandBuffer, bool aRemove); + + void RequestFrameCallbackLocked(const WaylandSurfaceLock& aProofOfLock); + void ClearFrameCallbackLocked(const WaylandSurfaceLock& aProofOfLock); + + void ClearInitialDrawCallbacksLocked(const WaylandSurfaceLock& aProofOfLock); + + void ClearScaleLocked(const WaylandSurfaceLock& aProofOfLock); + + // Weak ref to owning widget (nsWindow or NativeLayerWayland), + // used for diagnostics/logging only. + void* mLoggingWidget = nullptr; + + // TODO: Change to non-atomic and request lock? + mozilla::Atomic mIsMapped{false}; + mozilla::Atomic mIsReadyToDraw{false}; + + // Scaled surface size, ceiled or fractional. + // This reflects real surface size which we paint. + gfx::IntSize mSizeScaled; + + // Parent GdkWindow where we paint to, directly or via subsurface. + RefPtr mGdkWindow; + + // Parent wl_surface owned by mGdkWindow. It's used when we're attached + // directly to MozContainer. + wl_surface* mParentSurface = nullptr; + + // Parent WaylandSurface. + // + // Layer rendering (compositor) uses mSurface directly attached to + // wl_surface owned by mParent. + // + // For non-compositing rendering (old) mParent is WaylandSurface + // owned by parent nsWindow. + RefPtr mParent; + + // wl_surface setup/states + wl_surface* mSurface = nullptr; + bool mSurfaceNeedsCommit = false; + wl_subsurface* mSubsurface = nullptr; + gfx::IntPoint mSubsurfacePosition{-1, -1}; + + // Emulate frame callbacks until buffer is attached + // and we can get callbacks from compositor. + // That's important for VSync for instance to begin rendering + // to empty window. + bool mEmulateFrameCallback = true; + // Use emulated callbacks only. For testing purposes. + static bool sForceEmulateFrameCallback; + + // Wayland buffers attached to this surface AND held by Wayland compositor. + // There may be more than one buffer attached, for instance if + // previous buffer is hold by compositor. We need to keep + // there buffers live until compositor notify us that we + // can release them. + AutoTArray, 3> mAttachedBuffers; + + // It's kind of special case here where mSurface equal to mParentSurface + // so we directly paint to parent surface without subsurface. + // It's used when Wayland compositor doesn't support subsurfaces like D&D + // popups. This rendering setup is fragile and we want to use it as less as + // possible because we usually don't have control over parent surface. + // Calling code needs to make sure mParentSurface is valid and not + // used by Gtk/GtkWidget for instance. + bool mCommitToParentSurface = false; + + mozilla::Atomic mEGLWindow{nullptr}; + + bool mViewportFollowsSizeChanges = true; + wp_viewport* mViewport = nullptr; + gfx::Rect mViewportSourceRect{-1, -1, -1, -1}; + gfx::IntSize mViewportDestinationSize{-1, -1}; + + // Surface flip state on X/Y asix + bool mBufferTransformFlippedX = false; + bool mBufferTransformFlippedY = false; + + // Frame callback registered to parent surface. When we get it we know + // parent surface is ready and we can paint. + wl_callback* mInitialFrameCallback = nullptr; + std::vector> mInitialDrawCallbacks; + + // Frame callbacks of this surface + wl_callback* mFrameCallback = nullptr; + + struct FrameCallback { + std::function mCb; + bool mEmulated = false; + }; + + // Frame callback handlers called every frame + std::vector mPersistentFrameCallbackHandlers; + // Frame callback handlers called only once + std::vector mOneTimeFrameCallbackHandlers; + + xx_color_management_surface_v4* mColorSurface = nullptr; + xx_image_description_v4* mImageDescription = nullptr; + xx_image_description_creator_params_v4* mImageCreatorParams = nullptr; + + // WaylandSurface is used from Compositor/Rendering/Main threads. + mozilla::Mutex mMutex{"WaylandSurface"}; + WaylandSurfaceLock* mSurfaceLock = nullptr; + + // We may mark part of mSurface as opaque (non-transparent) if it's supported + // by Gtk which allows compositor to skip painting of covered parts. + bool mIsOpaqueSurfaceHandlerSet = false; + gulong mGdkAfterPaintId = 0; + static bool sIsOpaqueRegionEnabled; + static void (*sGdkWaylandWindowAddCallbackSurface)(GdkWindow*, + struct wl_surface*); + static void (*sGdkWaylandWindowRemoveCallbackSurface)(GdkWindow*, + struct wl_surface*); + guint mFrameCheckTimerID = 0; + constexpr static int sFrameCheckTimeoutMs = (int)(1000.0 / 60.0); + + // We use two scale systems in Firefox/Wayland. Ceiled (integer) scale and + // fractional scale. Ceiled scale is easy to implement but comes with + // rendering overhead while fractional rendering paints buffers with exact + // scale. + // + // Fractional scale is used as rendering optimization. + // For instance if 225% scale is used, ceiled scale is 3 + // and fractional 2.20. + // + // If we paint content with ceiled scale 3 and desktop uses scale 225%, + // Wayland compositor downscales buffer to 2.20 on rendering + // but we paint more pixels than neccessary (so we use name ceiled). + // + // Scale is used by wp_viewport. If a surface has a surface-local size + // of 100 px by 50 px and wishes to submit buffers with a scale of 1.5, + // then a buffer of 150px by 75 px should be used and the wp_viewport + // destination rectangle should be 100 px by 50 px. + // The wl_surface buffer scale should remain set to 1. + // + // For scale 2 (200%) we use surface size 200 x 100 px and set + // viewport size to 100 x 50 px. + // + // We're getting fractional scale number with a small delay from + // wp_fractional_scale_v1 after fist commit to surface. + // Meanwhile we can use ceiled scale number instead of fractional one or + // get fractional scale from parent window (if there's any). + // + enum class ScaleType { + Disabled, + Ceiled, + Fractional, + }; + + ScaleType mScaleType = ScaleType::Disabled; + + // mScreenScale is set from main thread only but read from + // different threads. + mozilla::Atomic mScreenScale{sNoScale}; + + wp_fractional_scale_v1* mFractionalScaleListener = nullptr; + + // mFractionalScaleCallback is called from + // wp_fractional_scale_v1_add_listener when scale is changed. + std::function mFractionalScaleCallback = []() {}; +}; + +} // namespace mozilla::widget + +#endif /* __MOZ_WAYLAND_SURFACE_H__ */ diff --git a/widget/gtk/WaylandSurface.cpp b/widget/gtk/WaylandSurface.cpp new file mode 100644 --- /dev/null +++ b/widget/gtk/WaylandSurface.cpp @@ -0,0 +1,1129 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "WaylandSurface.h" +#include "WaylandBuffer.h" +#include + +/* + TODO: + - Fractional popups has wrong location / are small + - Use set zero size Viewport skip rendering of empty attached base surface + - Remove compostor pauses from nsWindow + - Get initial scale from monitor (if parent is missing?) + - Layer Rendering - do we need emulated frame callback to start rendering? +*/ + +#undef LOG +#undef LOGVERBOSE + +#ifdef MOZ_LOGGING +# undef LOGWAYLAND + +# include "mozilla/Logging.h" +# include "nsTArray.h" +# include "Units.h" +# include "nsWindow.h" +extern mozilla::LazyLogModule gWidgetWaylandLog; +extern mozilla::LazyLogModule gWidgetLog; +# define LOGWAYLAND(str, ...) \ + MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, \ + ("%s: " str, GetDebugTag().get(), ##__VA_ARGS__)) +# define LOGVERBOSE(str, ...) \ + MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Verbose, \ + ("%s: " str, GetDebugTag().get(), ##__VA_ARGS__)) +# define LOGS(...) \ + MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) +# define LOGS_VERBOSE(...) \ + MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Verbose, (__VA_ARGS__)) +#else +# define LOGWAYLAND(...) +# define LOGVERBOSE(...) +# define LOGS(...) +# define LOGS_VERBOSE(...) +#endif /* MOZ_LOGGING */ + +using namespace mozilla; +using namespace mozilla::widget; + +namespace mozilla::widget { + +bool WaylandSurface::sForceEmulateFrameCallback = false; + +#ifdef MOZ_LOGGING +nsAutoCString WaylandSurface::GetDebugTag() const { + nsAutoCString tag; + tag.AppendPrintf("[%p]", mLoggingWidget); + return tag; +} +#endif + +void (*WaylandSurface::sGdkWaylandWindowAddCallbackSurface)( + GdkWindow*, struct wl_surface*) = nullptr; +void (*WaylandSurface::sGdkWaylandWindowRemoveCallbackSurface)( + GdkWindow*, struct wl_surface*) = nullptr; + +bool WaylandSurface::IsOpaqueRegionEnabled() { + static bool sIsOpaqueRegionEnabled = []() { + if (!StaticPrefs::widget_wayland_opaque_region_enabled_AtStartup()) { + return false; + } + sGdkWaylandWindowAddCallbackSurface = + reinterpret_cast(dlsym( + RTLD_DEFAULT, "gdk_wayland_window_add_frame_callback_surface")); + sGdkWaylandWindowRemoveCallbackSurface = + reinterpret_cast(dlsym( + RTLD_DEFAULT, "gdk_wayland_window_remove_frame_callback_surface")); + return sGdkWaylandWindowAddCallbackSurface && + sGdkWaylandWindowRemoveCallbackSurface; + }(); + return sIsOpaqueRegionEnabled; +} + +WaylandSurface::WaylandSurface(RefPtr aParent, + gfx::IntSize aSize) + : mSizeScaled(aSize), mParent(aParent) { + static std::once_flag onceFlag; + std::call_once(onceFlag, [&] { + sForceEmulateFrameCallback = + StaticPrefs::widget_wayland_emulate_frame_callback_AtStartup(); + }); + LOGWAYLAND("WaylandSurface::WaylandSurface(), parent [%p] size [%d x %d]", + mParent ? mParent->GetLoggingWidget() : nullptr, mSizeScaled.width, + mSizeScaled.height); +} + +WaylandSurface::~WaylandSurface() { + LOGWAYLAND("WaylandSurface::~WaylandSurface()"); + MOZ_RELEASE_ASSERT(!mIsMapped, "We can't release mapped WaylandSurface!"); + MOZ_RELEASE_ASSERT(!mSurfaceLock, "We can't release locked WaylandSurface!"); + MOZ_RELEASE_ASSERT(mAttachedBuffers.Length() == 0, + "We can't release surface with buffer attached!"); + MOZ_RELEASE_ASSERT(!mFrameCheckTimerID, + "We can't release WaylandSurface with active timer"); +} + +void WaylandSurface::InitialFrameCallbackHandler(struct wl_callback* callback) { + LOGWAYLAND( + "WaylandSurface::InitialFrameCallbackHandler() " + "mInitialFrameCallback %p mIsReadyToDraw %d initial_draw callback %zd\n", + (void*)mInitialFrameCallback, (bool)mIsReadyToDraw, + mInitialDrawCallbacks.size()); + + // We're supposed to run on main thread only. + AssertIsOnMainThread(); + + // mInitialFrameCallback/callback can be nullptr when redering directly + // to GtkWidget and InitialFrameCallbackHandler is called by us from main + // thread by WaylandSurface::Map(). + MOZ_RELEASE_ASSERT(mInitialFrameCallback == callback); + + std::vector> cbs; + { + WaylandSurfaceLock lock(this); + MozClearPointer(mInitialFrameCallback, wl_callback_destroy); + // It's possible that we're already unmapped so quit in such case. + if (!mSurface) { + LOGWAYLAND(" WaylandSurface is unmapped, quit."); + if (!mInitialDrawCallbacks.empty()) { + NS_WARNING("Unmapping WaylandSurface with active draw callback!"); + mInitialDrawCallbacks.clear(); + } + return; + } + if (mIsReadyToDraw) { + return; + } + mIsReadyToDraw = true; + cbs = std::move(mInitialDrawCallbacks); + } + + // We can't call the callbacks under lock +#ifdef MOZ_LOGGING + int callbackNum = 0; +#endif + for (auto const& cb : cbs) { + LOGWAYLAND(" initial callback fire [%d]", callbackNum++); + cb(); + } + + // If there's any frame callback waiting, register the handler now to fire + // them + if (!mPersistentFrameCallbackHandlers.empty() || + !mOneTimeFrameCallbackHandlers.empty()) { + LOGWAYLAND(" initial callback: Register regular frame callback"); + WaylandSurfaceLock lock(this); + RequestFrameCallbackLocked(lock); + } +} + +static void InitialFrameCallbackHandler(void* aWaylandSurface, + struct wl_callback* callback, + uint32_t time) { + auto* waylandSurface = static_cast(aWaylandSurface); + waylandSurface->InitialFrameCallbackHandler(callback); +} + +static const struct wl_callback_listener sWaylandSurfaceInitialFrameListener = { + ::InitialFrameCallbackHandler}; + +void WaylandSurface::AddInitialDrawCallbackLocked( + const WaylandSurfaceLock& aProofOfLock, + const std::function& aInitialDrawCB) { + LOGVERBOSE("WaylandSurface::AddInitialDrawCallbackLocked()"); + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + + if (mIsReadyToDraw && !mSurface) { + NS_WARNING( + "WaylandSurface::AddInitialDrawCallbackLocked():" + " ready to draw without wayland surface!"); + } + MOZ_DIAGNOSTIC_ASSERT(!mIsReadyToDraw || !mSurface); + mInitialDrawCallbacks.push_back(aInitialDrawCB); +} + +void WaylandSurface::AddOrFireInitialDrawCallback( + const std::function& aInitialDrawCB) { + { + WaylandSurfaceLock lock(this); + if (mIsReadyToDraw && !mSurface) { + NS_WARNING( + "WaylandSurface::AddOrFireInitialDrawCallback(): ready to draw " + "without wayland surface!"); + } + if (!mIsReadyToDraw || !mSurface) { + LOGVERBOSE( + "WaylandSurface::AddOrFireInitialDrawCallback() callback stored"); + mInitialDrawCallbacks.push_back(aInitialDrawCB); + return; + } + } + + LOGWAYLAND("WaylandSurface::AddOrFireInitialDrawCallback() callback fire"); + + // We're ready to draw and we have a surface to draw into. + aInitialDrawCB(); +} + +void WaylandSurface::ClearInitialDrawCallbacksLocked( + const WaylandSurfaceLock& aProofOfLock) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + MozClearPointer(mInitialFrameCallback, wl_callback_destroy); + mInitialDrawCallbacks.clear(); +} + +void WaylandSurface::ClearInitialDrawCallbacks() { + WaylandSurfaceLock lock(this); + ClearInitialDrawCallbacksLocked(lock); +} + +void WaylandSurface::FrameCallbackHandler(struct wl_callback* aCallback, + uint32_t aTime, + bool aRoutedFromChildSurface) { + // aCallback is nullptr if it's emulated or called from external source + MOZ_DIAGNOSTIC_ASSERT(aCallback == nullptr || mFrameCallback == aCallback); + + // We're supposed to run on main thread only. + AssertIsOnMainThread(); + + bool emulatedCallback = !aCallback && !aTime; + + std::vector cbs; + { + WaylandSurfaceLock lock(this); + LOGVERBOSE( + "WaylandSurface::FrameCallbackHandler() one-time %zd " + "persistent %zd emulated %d routed %d", + mOneTimeFrameCallbackHandlers.size(), + mPersistentFrameCallbackHandlers.size(), emulatedCallback, + aRoutedFromChildSurface); + + // Frame callback is not emulated and comes from this surface, + // so we don't need to run emulation any more. + if (!emulatedCallback && !aRoutedFromChildSurface) { + MOZ_DIAGNOSTIC_ASSERT(!sForceEmulateFrameCallback); + mEmulateFrameCallback = false; + } + // Don't run emulated callbacks on hidden surfaces + if (emulatedCallback && !mIsMapped) { + return; + } + if (aCallback) { + ClearFrameCallbackLocked(lock); + } + + if (emulatedCallback) { + mOneTimeFrameCallbackHandlers.erase( + std::remove_if(mOneTimeFrameCallbackHandlers.begin(), + mOneTimeFrameCallbackHandlers.end(), + [&](FrameCallback const& cb) { + if (cb.mEmulated) { + cbs.push_back(cb); + } + return cb.mEmulated; + }), + mOneTimeFrameCallbackHandlers.end()); + } else { + cbs = std::move(mOneTimeFrameCallbackHandlers); + } + std::copy(mPersistentFrameCallbackHandlers.begin(), + mPersistentFrameCallbackHandlers.end(), back_inserter(cbs)); + } + + // We can't run the callbacks under WaylandSurfaceLock +#ifdef MOZ_LOGGING + int callbackNum = 0; +#endif + for (auto const& cb : cbs) { + LOGVERBOSE(" frame callback fire [%d]", callbackNum++); + const auto& [callback, runEmulated] = cb; + if (emulatedCallback && !runEmulated) { + continue; + } + callback(aCallback, aTime); + } + + // Fire frame callback again if there's any pending frame callback + if (!mPersistentFrameCallbackHandlers.empty() || + !mOneTimeFrameCallbackHandlers.empty()) { + WaylandSurfaceLock lock(this); + RequestFrameCallbackLocked(lock); + } +} + +static void FrameCallbackHandler(void* aWaylandSurface, + struct wl_callback* aCallback, + uint32_t aTime) { + RefPtr waylandSurface = static_cast(aWaylandSurface); + waylandSurface->FrameCallbackHandler(aCallback, aTime, + /* aRoutedFromChildSurface */ false); +} + +static const struct wl_callback_listener sWaylandSurfaceFrameListener = { + ::FrameCallbackHandler}; + +void WaylandSurface::RequestFrameCallbackLocked( + const WaylandSurfaceLock& aProofOfLock) { + LOGVERBOSE( + "WaylandSurface::RequestFrameCallbackLocked(), mapped %d emulate %d " + "frame " + "%d", + !!mIsMapped, mEmulateFrameCallback, !!mFrameCallback); + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + if (!mIsMapped) { + // Frame callback will be added by initial frame callback after Map + // or we're unmapped. + return; + } + MOZ_DIAGNOSTIC_ASSERT(mSurface, "Missing mapped surface!"); + if (!mFrameCallback && !sForceEmulateFrameCallback) { + mFrameCallback = wl_surface_frame(mSurface); + wl_callback_add_listener(mFrameCallback, &sWaylandSurfaceFrameListener, + this); + mSurfaceNeedsCommit = true; + } + + if (mEmulateFrameCallback && !mFrameCheckTimerID) { + LOGVERBOSE( + "WaylandSurface::RequestFrameCallbackLocked() emulated, schedule " + "next check"); + // Frame callback needs to be run from main thread + NS_DispatchToMainThread(NS_NewRunnableFunction( + "WaylandSurface::RequestFrameCallbackLocked", + [this, self = RefPtr{this}]() { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + WaylandSurfaceLock lock(this); + if (mIsMapped && !mFrameCheckTimerID) { + mFrameCheckTimerID = g_timeout_add( + sFrameCheckTimeoutMs, + [](void* data) -> gint { + RefPtr surface = static_cast(data); + LOGS_VERBOSE("[%p]: WaylandSurface::Fallback frame callback", + surface->GetLoggingWidget()); + // Clear timer ID as we're going to remove this timer + surface->mFrameCheckTimerID = 0; + surface->FrameCallbackHandler( + nullptr, 0, /* aEmulateFrameCallback */ false); + return G_SOURCE_REMOVE; + }, + this); + } + })); + } +} + +void WaylandSurface::ClearFrameCallbackLocked( + const WaylandSurfaceLock& aProofOfLock) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + MozClearPointer(mFrameCallback, wl_callback_destroy); +} + +void WaylandSurface::AddOneTimeFrameCallbackLocked( + const WaylandSurfaceLock& aProofOfLock, + const std::function& aFrameCallbackHandler, + bool aEmulateFrameCallback) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + LOGWAYLAND("WaylandSurface::AddOneTimeFrameCallbackLocked()"); + + mOneTimeFrameCallbackHandlers.push_back( + FrameCallback{aFrameCallbackHandler, aEmulateFrameCallback}); + RequestFrameCallbackLocked(aProofOfLock); +} + +void WaylandSurface::AddPersistentFrameCallbackLocked( + const WaylandSurfaceLock& aProofOfLock, + const std::function& aFrameCallbackHandler, + bool aEmulateFrameCallback) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + LOGWAYLAND("WaylandSurface::AddPersistentFrameCallbackLocked()"); + + mPersistentFrameCallbackHandlers.push_back( + FrameCallback{aFrameCallbackHandler, aEmulateFrameCallback}); + RequestFrameCallbackLocked(aProofOfLock); +} + +bool WaylandSurface::CreateViewportLocked( + const WaylandSurfaceLock& aProofOfLock, bool aFollowsSizeChanges) { + LOGWAYLAND("WaylandSurface::CreateViewportLocked() follow size %d", + aFollowsSizeChanges); + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + MOZ_DIAGNOSTIC_ASSERT(mIsMapped); + MOZ_DIAGNOSTIC_ASSERT(!mViewport); + + auto* viewPorter = WaylandDisplayGet()->GetViewporter(); + if (viewPorter) { + mViewport = wp_viewporter_get_viewport(viewPorter, mSurface); + } + if (!mViewport) { + LOGWAYLAND( + "WaylandSurface::CreateViewportLocked(): Failed to get " + "WaylandViewport!"); + return false; + } + mSurfaceNeedsCommit = true; + mViewportFollowsSizeChanges = aFollowsSizeChanges; + return !!mViewport; +} + +bool WaylandSurface::MapLocked(const WaylandSurfaceLock& aProofOfLock, + GdkWindow* aGdkWindow, + wl_surface* aParentWLSurface, + WaylandSurfaceLock* aParentWaylandSurfaceLock, + gfx::IntPoint aSubsurfacePosition, + bool aCommitToParent, bool aSubsurfaceDesync, + bool aRegisterAsOpaqueSurface) { + LOGWAYLAND("WaylandSurface::MapLocked()"); + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + + // We're already mapped, so just update subsurface position. + if (mIsMapped) { + if (mCommitToParentSurface) { + return true; + } + wl_surface* newParentSurface = + aParentWLSurface + ? aParentWLSurface + : aParentWaylandSurfaceLock->GetWaylandSurface()->mSurface; + MOZ_RELEASE_ASSERT(mParentSurface == newParentSurface); + MoveLocked(aProofOfLock, aSubsurfacePosition); + return true; + } + + MOZ_DIAGNOSTIC_ASSERT(!(aParentWLSurface && aParentWaylandSurfaceLock), + "Only one parent can be used."); + MOZ_DIAGNOSTIC_ASSERT(aGdkWindow, + "Missing parent GdkWindow, parent is unmapped?"); + MOZ_DIAGNOSTIC_ASSERT(!mSurface && !mSubsurface, "Already mapped?"); + + mGdkWindow = aGdkWindow; + + if (aParentWLSurface) { + mParentSurface = aParentWLSurface; + } else { + MOZ_DIAGNOSTIC_ASSERT(!mParentSurface, "Already mapped?"); + mParent = aParentWaylandSurfaceLock->GetWaylandSurface(); + MOZ_DIAGNOSTIC_ASSERT(mParent->IsMapped(), "Parent surface is not mapped?"); + mParentSurface = mParent->mSurface; + } + + mCommitToParentSurface = aCommitToParent; + mSubsurfacePosition = aSubsurfacePosition; + + if (mCommitToParentSurface) { + LOGWAYLAND(" commit to parent"); + mIsMapped = true; + mSurface = mParentSurface; + NS_DispatchToCurrentThread(NS_NewRunnableFunction( + "InitialFrameCallbackHandler", [self = RefPtr{this}]() { + self->InitialFrameCallbackHandler(nullptr); + })); + return true; + } + + // Available as of GTK 3.8+ + struct wl_compositor* compositor = WaylandDisplayGet()->GetCompositor(); + mSurface = wl_compositor_create_surface(compositor); + if (!mSurface) { + LOGWAYLAND(" Failed - can't create surface!"); + return false; + } + + mSubsurface = wl_subcompositor_get_subsurface( + WaylandDisplayGet()->GetSubcompositor(), mSurface, mParentSurface); + if (!mSubsurface) { + MozClearPointer(mSurface, wl_surface_destroy); + LOGWAYLAND(" Failed - can't create sub-surface!"); + return false; + } + if (aSubsurfaceDesync) { + wl_subsurface_set_desync(mSubsurface); + } + wl_subsurface_set_position(mSubsurface, mSubsurfacePosition.x, + mSubsurfacePosition.y); + + mInitialFrameCallback = wl_surface_frame(mParentSurface); + wl_callback_add_listener(mInitialFrameCallback, + &sWaylandSurfaceInitialFrameListener, this); + LOGWAYLAND(" created frame callback ID %d\n", + wl_proxy_get_id((struct wl_proxy*)mInitialFrameCallback)); + + CommitLocked(aProofOfLock, /* aForceCommit */ true, + /* aForceDisplayFlush */ true); + + if (aRegisterAsOpaqueSurface) { + MOZ_DIAGNOSTIC_ASSERT(mGdkWindow); + AddOpaqueSurfaceHandlerLocked(aProofOfLock); + } + + mIsMapped = true; + + LOGWAYLAND(" created surface %p ID %d", (void*)mSurface, + wl_proxy_get_id((struct wl_proxy*)mSurface)); + return true; +} + +bool WaylandSurface::MapLocked(const WaylandSurfaceLock& aProofOfLock, + GdkWindow* aGdkWindow, + wl_surface* aParentWLSurface, + gfx::IntPoint aSubsurfacePosition, + bool aCommitToParent) { + return MapLocked(aProofOfLock, aGdkWindow, aParentWLSurface, nullptr, + aSubsurfacePosition, aCommitToParent, + /* aSubsurfaceDesync */ true, + /* aRegisterAsOpaqueSurface */ true); +} + +bool WaylandSurface::MapLocked(const WaylandSurfaceLock& aProofOfLock, + GdkWindow* aGdkWindow, + WaylandSurfaceLock* aParentWaylandSurfaceLock, + gfx::IntPoint aSubsurfacePosition) { + return MapLocked(aProofOfLock, aGdkWindow, nullptr, aParentWaylandSurfaceLock, + aSubsurfacePosition, + /* aCommitToParent */ false, + /* aSubsurfaceDesync */ true, + /* aRegisterAsOpaqueSurface */ false); +} + +void WaylandSurface::Unmap() { + if (!mIsMapped) { + return; + } + + WaylandSurfaceLock lock(this); + LOGWAYLAND("WaylandSurface::Unmap()"); + mIsMapped = false; + + // If mCommitToParentSurface is set, mSurface may be already deleted as + // unamp/hide Gtk handler is called before us and we can't do anything + // with it (at least I don't know how to override it). + // So make it cleat and don't use it. + // It doesn't matter much as we use direct rendering for D&D popups only. + if (mCommitToParentSurface) { + mSurface = nullptr; + } + + ClearInitialDrawCallbacksLocked(lock); + RemoveOpaqueSurfaceHandlerLocked(lock); + ClearFrameCallbackLocked(lock); + ClearScaleLocked(lock); + + MozClearPointer(mViewport, wp_viewport_destroy); + mViewportDestinationSize = gfx::IntSize(-1, -1); + mViewportSourceRect = gfx::Rect(-1, -1, -1, -1); + + wl_egl_window* tmp = nullptr; + mEGLWindow.exchange(tmp); + MozClearPointer(tmp, wl_egl_window_destroy); + MozClearPointer(mFractionalScaleListener, wp_fractional_scale_v1_destroy); + MozClearPointer(mSubsurface, wl_subsurface_destroy); + MozClearPointer(mSurface, wl_surface_destroy); + + // Remove references to WaylandBuffers attached to mSurface. + // We may not get buffer release callback when wl_surface is destroyed. + // Also compositors may fail to send wl_buffer release callback sometime + // which leads to memleaks. + mAttachedBuffers.Clear(); + + mGdkWindow = nullptr; + mIsReadyToDraw = false; + mEmulateFrameCallback = true; + MozClearHandleID(mFrameCheckTimerID, g_source_remove); +} + +void WaylandSurface::Commit(WaylandSurfaceLock* aProofOfLock, bool aForceCommit, + bool aForceDisplayFlush) { + MOZ_DIAGNOSTIC_ASSERT(aProofOfLock == mSurfaceLock); + // mSurface may be already deleted, see WaylandSurface::Unmap(); + if (mSurface && (aForceCommit || mSurfaceNeedsCommit)) { + LOGVERBOSE( + "WaylandSurface::Commit() needs commit %d, force commit %d flush %d", + mSurfaceNeedsCommit, aForceCommit, aForceDisplayFlush); + mSurfaceNeedsCommit = false; + wl_surface_commit(mSurface); + if (aForceDisplayFlush) { + wl_display_flush(WaylandDisplayGet()->GetDisplay()); + } + } +} + +void WaylandSurface::CommitLocked(const WaylandSurfaceLock& aProofOfLock, + bool aForceCommit, bool aForceDisplayFlush) { + Commit((WaylandSurfaceLock*)&aProofOfLock, aForceCommit, aForceDisplayFlush); +} + +void WaylandSurface::MoveLocked(const WaylandSurfaceLock& aProofOfLock, + gfx::IntPoint& aPosition) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + MOZ_DIAGNOSTIC_ASSERT(mIsMapped); + + if (mSubsurfacePosition == aPosition || mCommitToParentSurface) { + return; + } + + MOZ_DIAGNOSTIC_ASSERT(mSubsurface); + LOGWAYLAND("WaylandSurface::MoveLocked() [%d,%d]", (int)aPosition.x, + (int)aPosition.y); + mSubsurfacePosition = aPosition; + wl_subsurface_set_position(mSubsurface, aPosition.x, aPosition.y); + mSurfaceNeedsCommit = true; +} + +// Route input to parent wl_surface owned by Gtk+ so we get input +// events from Gtk+. +bool WaylandSurface::DisableUserInputLocked( + const WaylandSurfaceLock& aProofOfLock) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + wl_region* region = + wl_compositor_create_region(WaylandDisplayGet()->GetCompositor()); + wl_surface_set_input_region(mSurface, region); + wl_region_destroy(region); + mSurfaceNeedsCommit = true; + return true; +} + +void WaylandSurface::SetOpaqueRegionLocked( + const WaylandSurfaceLock& aProofOfLock, const gfx::IntRegion& aRegion) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + if (!mSurface || !IsOpaqueRegionEnabled()) { + return; + } + // Region should be in surface-logical coordinates, so we need to divide by + // the buffer scale. We use round-in in order to be safe with subpixels. + UnknownScaleFactor scale(GetScale()); + wl_region* region = + wl_compositor_create_region(WaylandDisplayGet()->GetCompositor()); + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + const auto& rect = gfx::RoundedIn(iter.Get().ToUnknownRect() / scale); + wl_region_add(region, rect.x, rect.y, rect.Width(), rect.Height()); + } + wl_surface_set_opaque_region(mSurface, region); + wl_region_destroy(region); + mSurfaceNeedsCommit = true; +} + +void WaylandSurface::SetOpaqueRegion(const gfx::IntRegion& aRegion) { + WaylandSurfaceLock lock(this); + SetOpaqueRegionLocked(lock, aRegion); +} + +void WaylandSurface::SetOpaqueLocked(const WaylandSurfaceLock& aProofOfLock) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + if (!mSurface || !IsOpaqueRegionEnabled()) { + return; + } + wl_region* region = + wl_compositor_create_region(WaylandDisplayGet()->GetCompositor()); + wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_set_opaque_region(mSurface, region); + wl_region_destroy(region); + mSurfaceNeedsCommit = true; +} + +void WaylandSurface::FractionalScaleHandler(void* data, + struct wp_fractional_scale_v1* info, + uint32_t wire_scale) { + AssertIsOnMainThread(); + + WaylandSurface* waylandSurface = static_cast(data); + waylandSurface->mScreenScale = wire_scale / 120.0; + + LOGS("[%p]: WaylandSurface::FractionalScaleHandler() scale: %f\n", + waylandSurface->mLoggingWidget, (double)waylandSurface->mScreenScale); + + waylandSurface->mFractionalScaleCallback(); +} + +static const struct wp_fractional_scale_v1_listener fractional_scale_listener = + { + .preferred_scale = WaylandSurface::FractionalScaleHandler, +}; + +bool WaylandSurface::EnableFractionalScaleLocked( + const WaylandSurfaceLock& aProofOfLock, + std::function aFractionalScaleCallback, bool aManageViewport) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + + MOZ_DIAGNOSTIC_ASSERT(!mFractionalScaleListener); + auto* manager = WaylandDisplayGet()->GetFractionalScaleManager(); + if (!manager) { + LOGWAYLAND( + "WaylandSurface::SetupFractionalScale(): Failed to get " + "FractionalScaleManager"); + return false; + } + mFractionalScaleListener = + wp_fractional_scale_manager_v1_get_fractional_scale(manager, mSurface); + wp_fractional_scale_v1_add_listener(mFractionalScaleListener, + &fractional_scale_listener, this); + + // Create Viewport with aFollowsSizeChanges enabled, + // regular rendering uses Viewport for fraction scale only. + if (aManageViewport && + !CreateViewportLocked(aProofOfLock, /* aFollowsSizeChanges */ true)) { + return false; + } + mFractionalScaleCallback = std::move(aFractionalScaleCallback); + + // Init scale to default values and load ceiled screen scale from GdkWindow. + // We use it as fallback before we get mScreenScale from system. + mScaleType = ScaleType::Fractional; + + LOGWAYLAND("WaylandSurface::SetupFractionalScale()"); + return true; +} + +bool WaylandSurface::EnableCeiledScaleLocked( + const WaylandSurfaceLock& aProofOfLock) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + + if (!CreateViewportLocked(aProofOfLock, /* aFollowsSizeChanges */ true)) { + return false; + } + + mScaleType = ScaleType::Disabled; + + LOGWAYLAND("WaylandSurface::EnableCeiledScaleLocked()"); + return true; +} + +void WaylandSurface::ClearScaleLocked(const WaylandSurfaceLock& aProofOfLock) { + LOGWAYLAND("WaylandSurface::ClearScaleLocked()"); + mFractionalScaleCallback = []() {}; + mScreenScale = sNoScale; +} + +void WaylandSurface::SetCeiledScaleLocked( + const WaylandSurfaceLock& aProofOfLock, int aScreenCeiledScale) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + + // Allow to set scale for unmapped surfaces unconditionally. + // It sets initial scale factor so we have something to work with. + if (!mIsMapped || IsCeiledScaleLocked(aProofOfLock)) { + // mScreenScale = (double)aScreenCeiledScale; + mScreenScale = aScreenCeiledScale; + LOGWAYLAND("WaylandSurface::SetCeiledScaleLocked() scale %f", + (double)mScreenScale); + } +} + +void WaylandSurface::SetSizeLocked(const WaylandSurfaceLock& aProofOfLock, + gfx::IntSize aSizeScaled, + gfx::IntSize aSizeUnscaled) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + LOGVERBOSE( + "WaylandSurface::SetSizeLocked(): Size [%d x %d] unscaled size [%d x %d]", + aSizeScaled.width, aSizeScaled.height, aSizeUnscaled.width, + aSizeUnscaled.height); + mSizeScaled = aSizeScaled; + if (mViewportFollowsSizeChanges) { + SetViewPortDestLocked(aProofOfLock, aSizeUnscaled); + } +} + +void WaylandSurface::SetViewPortDestLocked( + const WaylandSurfaceLock& aProofOfLock, gfx::IntSize aDestSize) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + if (mViewport) { + if (mViewportDestinationSize == aDestSize) { + return; + } + LOGWAYLAND("WaylandSurface::SetViewPortDestLocked(): Size [%d x %d]", + aDestSize.width, aDestSize.height); + mViewportDestinationSize = aDestSize; + if (!mViewportDestinationSize.width || !mViewportDestinationSize.height) { + mViewportDestinationSize.width = mViewportDestinationSize.height = -1; + } + wp_viewport_set_destination(mViewport, mViewportDestinationSize.width, + mViewportDestinationSize.height); + mSurfaceNeedsCommit = true; + } +} + +void WaylandSurface::SetViewPortSourceRectLocked( + const WaylandSurfaceLock& aProofOfLock, gfx::Rect aRect) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + + if (!mViewport || mViewportSourceRect == aRect) { + return; + } + + LOGWAYLAND( + "WaylandSurface::SetViewPortSourceRectLocked(): [%f, %f] -> [%f x %f]", + aRect.x, aRect.y, aRect.width, aRect.height); + mViewportSourceRect = aRect; + wp_viewport_set_source( + mViewport, wl_fixed_from_double(aRect.x), wl_fixed_from_double(aRect.y), + wl_fixed_from_double(aRect.width), wl_fixed_from_double(aRect.height)); + mSurfaceNeedsCommit = true; +} + +wl_surface* WaylandSurface::Lock(WaylandSurfaceLock* aWaylandSurfaceLock) + // Disable thread safety analysis, it reports: + // mutex 'mMutex' is still held at the end of function + // which we want. + MOZ_NO_THREAD_SAFETY_ANALYSIS { + mMutex.Lock(); + mSurfaceLock = aWaylandSurfaceLock; + return mIsReadyToDraw ? mSurface : nullptr; +} + +void WaylandSurface::Unlock(struct wl_surface** aSurface, + WaylandSurfaceLock* aWaylandSurfaceLock) { + MOZ_DIAGNOSTIC_ASSERT(*aSurface == nullptr || mSurface == nullptr || + *aSurface == mSurface); + MOZ_DIAGNOSTIC_ASSERT(mSurfaceLock == aWaylandSurfaceLock); + mMutex.AssertCurrentThreadOwns(); + if (*aSurface) { + *aSurface = nullptr; + } + mSurfaceLock = nullptr; + mMutex.Unlock(); +} + +void WaylandSurface::OpaqueSurfaceHandler(GdkFrameClock* aClock, void* aData) { + auto* waylandSurface = static_cast(aData); + if (waylandSurface->IsMapped()) { + LOGS("[%p]: WaylandSurface::OpaqueSurfaceHandler()", + waylandSurface->mLoggingWidget); + WaylandSurfaceLock lock(waylandSurface); + waylandSurface->CommitLocked(lock, /* aForceCommit */ true); + } +} + +bool WaylandSurface::AddOpaqueSurfaceHandlerLocked( + const WaylandSurfaceLock& aProofOfLock, bool aRegisterCommitHandler) { + if (!IsOpaqueRegionEnabled() || mIsOpaqueSurfaceHandlerSet) { + return false; + } + + LOGWAYLAND( + "WaylandSurface::AddOpaqueSurfaceHandlerLocked() aRegisterCommitHandler " + "%d", + aRegisterCommitHandler); + + AssertIsOnMainThread(); + sGdkWaylandWindowAddCallbackSurface(mGdkWindow, mSurface); + mIsOpaqueSurfaceHandlerSet = true; + + if (aRegisterCommitHandler) { + MOZ_DIAGNOSTIC_ASSERT(!mGdkAfterPaintId); + mGdkAfterPaintId = g_signal_connect_after( + gdk_window_get_frame_clock(mGdkWindow), "after-paint", + G_CALLBACK(WaylandSurface::OpaqueSurfaceHandler), this); + } + return true; +} + +bool WaylandSurface::RemoveOpaqueSurfaceHandlerLocked( + const WaylandSurfaceLock& aProofOfLock) { + if (!IsOpaqueRegionEnabled() || !mIsOpaqueSurfaceHandlerSet) { + return false; + } + AssertIsOnMainThread(); + if (mSurface) { + LOGWAYLAND("WaylandSurface::RemoveOpaqueSurfaceHandlerLocked()"); + sGdkWaylandWindowRemoveCallbackSurface(mGdkWindow, mSurface); + mIsOpaqueSurfaceHandlerSet = false; + } + if (mGdkAfterPaintId) { + GdkFrameClock* frameClock = gdk_window_get_frame_clock(mGdkWindow); + // If we're already unmapped frameClock is nullptr + if (frameClock) { + g_signal_handler_disconnect(frameClock, mGdkAfterPaintId); + } + mGdkAfterPaintId = 0; + } + return true; +} + +wl_egl_window* WaylandSurface::GetEGLWindow(nsIntSize aUnscaledSize) { + LOGWAYLAND("WaylandSurface::GetEGLWindow() eglwindow %p", (void*)mEGLWindow); + + WaylandSurfaceLock lock(this); + if (!mSurface || !mIsReadyToDraw) { + LOGWAYLAND(" quit, mSurface %p mIsReadyToDraw %d", mSurface, + (bool)mIsReadyToDraw); + return nullptr; + } + + auto scale = GetScale(); + // TODO: Correct size rounding + nsIntSize scaledSize((int)floor(aUnscaledSize.width * scale), + (int)floor(aUnscaledSize.height * scale)); + if (!mEGLWindow) { + mEGLWindow = + wl_egl_window_create(mSurface, scaledSize.width, scaledSize.height); + LOGWAYLAND( + "WaylandSurface::GetEGLWindow() created eglwindow [%p] size %d x %d", + (void*)mEGLWindow, scaledSize.width, scaledSize.height); + } else { + LOGWAYLAND("WaylandSurface::GetEGLWindow() resized to %d x %d", + scaledSize.width, scaledSize.height); + wl_egl_window_resize(mEGLWindow, scaledSize.width, scaledSize.height, 0, 0); + } + + if (mEGLWindow) { + SetSizeLocked(lock, scaledSize, aUnscaledSize); + } + + return mEGLWindow; +} + +// Return false if scale factor doesn't match buffer size. +// We need to skip painting in such case do avoid Wayland compositor freaking. +bool WaylandSurface::SetEGLWindowSize(nsIntSize aScaledSize) { + WaylandSurfaceLock lock(this); + + // We may be called after unmap so we're missing egl window completelly. + // In such case don't return false which would block compositor. + // We return true here and don't block flush WebRender queue. + // We'll be repainted if our window become visible again anyway. + if (!mEGLWindow) { + return true; + } + + auto scale = GetScale(); + // TODO: Avoid precision lost here? Load coordinates from window? + nsIntSize unscaledSize((int)round(aScaledSize.width / scale), + (int)round(aScaledSize.height / scale)); + + LOGVERBOSE( + "WaylandSurface::SetEGLWindowSize() scaled [%d x %d] unscaled [%d x %d] " + "scale %f", + aScaledSize.width, aScaledSize.height, unscaledSize.width, + unscaledSize.height, scale); + + wl_egl_window_resize(mEGLWindow, aScaledSize.width, aScaledSize.height, 0, 0); + SetSizeLocked(lock, aScaledSize, unscaledSize); + return true; +} + +void WaylandSurface::InvalidateRegionLocked( + const WaylandSurfaceLock& aProofOfLock, + const gfx::IntRegion& aInvalidRegion) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + MOZ_DIAGNOSTIC_ASSERT(mSurface); + + if (mCommitToParentSurface) { + // When committing to parent surface we must use wl_surface_damage(). + // A parent surface is created as v.3 object which does not support + // wl_surface_damage_buffer(). + wl_surface_damage(mSurface, 0, 0, INT32_MAX, INT32_MAX); + } else { + for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) { + gfx::IntRect r = iter.Get(); + wl_surface_damage_buffer(mSurface, r.x, r.y, r.width, r.height); + } + } + mSurfaceNeedsCommit = true; +} + +void WaylandSurface::InvalidateLocked(const WaylandSurfaceLock& aProofOfLock) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + MOZ_DIAGNOSTIC_ASSERT(mSurface); + + wl_surface_damage(mSurface, 0, 0, INT32_MAX, INT32_MAX); + mSurfaceNeedsCommit = true; +} + +bool WaylandSurface::CheckAndRemoveWaylandBuffer(WaylandBuffer* aWaylandBuffer, + bool aRemove) { + for (size_t i = 0; i < mAttachedBuffers.Length(); i++) { + if (mAttachedBuffers[i] == aWaylandBuffer) { + if (aRemove) { + MOZ_DIAGNOSTIC_ASSERT(!mAttachedBuffers[i]->IsAttached(), + "Removing attached buffer!"); + mAttachedBuffers.RemoveElementAt(i); + } + return true; + } + } + return false; +} + +bool WaylandSurface::AttachLocked(const WaylandSurfaceLock& aProofOfLock, + RefPtr aWaylandBuffer) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + MOZ_DIAGNOSTIC_ASSERT(mSurface); + + wl_buffer* buffer = aWaylandBuffer->BorrowBuffer(this); + if (!buffer) { + LOGWAYLAND("WaylandSurface::AttachLocked() failed, BorrowBuffer() failed"); + return false; + } + + auto scale = GetScale(); + MOZ_DIAGNOSTIC_ASSERT(scale != sNoScale); + + LayoutDeviceIntSize bufferSize = aWaylandBuffer->GetSize(); + // TODO: rounding? + SetSizeLocked(aProofOfLock, gfx::IntSize(bufferSize.width, bufferSize.height), + gfx::IntSize((int)round(bufferSize.width / scale), + (int)round(bufferSize.height / scale))); + LOGWAYLAND( + "WaylandSurface::AttachLocked() WaylandBuffer [%p] size [%d x %d] " + "fractional scale %f", + aWaylandBuffer.get(), bufferSize.width, bufferSize.height, scale); + + // Take reference to buffer until it's released by compositor + MOZ_DIAGNOSTIC_ASSERT( + !CheckAndRemoveWaylandBuffer(aWaylandBuffer, /* aRemove */ false), + "Wayland buffer is already attached?"); + mAttachedBuffers.AppendElement(std::move(aWaylandBuffer)); + if (mCommitToParentSurface) { + wl_surface_set_buffer_scale(mSurface, 1); + } + wl_surface_attach(mSurface, buffer, 0, 0); + mSurfaceNeedsCommit = true; + + mEmulateFrameCallback = sForceEmulateFrameCallback; + return true; +} + +void WaylandSurface::RemoveAttachedBufferLocked( + const WaylandSurfaceLock& aProofOfLock) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + MOZ_DIAGNOSTIC_ASSERT(mSurface); + + LOGWAYLAND("WaylandSurface::RemoveAttachedBufferLocked()"); + + // Set our size according to attached buffer + SetSizeLocked(aProofOfLock, gfx::IntSize(0, 0), gfx::IntSize(0, 0)); + wl_surface_attach(mSurface, nullptr, 0, 0); + mSurfaceNeedsCommit = true; + + mEmulateFrameCallback = true; +} + +void WaylandSurface::DetachedLocked(const WaylandSurfaceLock& aProofOfLock, + RefPtr aWaylandBuffer) { + LOGWAYLAND("WaylandSurface::DetachedLocked() WaylandBuffer [%p]", + aWaylandBuffer.get()); + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + + bool removed = + CheckAndRemoveWaylandBuffer(aWaylandBuffer, /* aRemove */ true); + MOZ_DIAGNOSTIC_ASSERT(removed, "Wayland buffer is not attached?"); +} + +// Place this WaylandSurface above aLowerSurface +void WaylandSurface::PlaceAboveLocked(const WaylandSurfaceLock& aProofOfLock, + WaylandSurfaceLock& aLowerSurfaceLock) { + LOGWAYLAND("WaylandSurface::PlaceAboveLocked() aLowerSurface [%p]", + aLowerSurfaceLock.GetWaylandSurface()); + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + MOZ_DIAGNOSTIC_ASSERT(mSubsurface); + + // WaylandSurface is reffed by WaylandSurfaceLock + WaylandSurface* lowerSurface = aLowerSurfaceLock.GetWaylandSurface(); + // It's possible that lowerSurface becomed unmapped. In such rare case + // just skip the operation, we may be deleted anyway. + if (lowerSurface->mSurface) { + wl_subsurface_place_above(mSubsurface, lowerSurface->mSurface); + } + mSurfaceNeedsCommit = true; +} + +void WaylandSurface::SetTransformFlippedLocked( + const WaylandSurfaceLock& aProofOfLock, bool aFlippedX, bool aFlippedY) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + if (aFlippedX == mBufferTransformFlippedX && + aFlippedY == mBufferTransformFlippedY) { + return; + } + + MOZ_RELEASE_ASSERT(mSurface); + + mBufferTransformFlippedX = aFlippedX; + mBufferTransformFlippedY = aFlippedY; + + if (mBufferTransformFlippedY) { + if (mBufferTransformFlippedX) { + wl_surface_set_buffer_transform(mSurface, WL_OUTPUT_TRANSFORM_180); + } else { + wl_surface_set_buffer_transform(mSurface, + WL_OUTPUT_TRANSFORM_FLIPPED_180); + } + } else { + if (mBufferTransformFlippedX) { + wl_surface_set_buffer_transform(mSurface, WL_OUTPUT_TRANSFORM_FLIPPED); + } else { + wl_surface_set_buffer_transform(mSurface, WL_OUTPUT_TRANSFORM_NORMAL); + } + } +} + +GdkWindow* WaylandSurface::GetGdkWindow() const { + // Gdk/Gtk code is used on main thread only + AssertIsOnMainThread(); + return mGdkWindow; +} + +double WaylandSurface::GetScale() { + if (mScreenScale != sNoScale) { + LOGVERBOSE("WaylandSurface::GetScale() fractional scale %f", + (double)mScreenScale); + return mScreenScale; + } + + // We don't have scale yet - query parent surface if there's any. + if (mParent) { + auto scale = mParent->GetScale(); + LOGVERBOSE("WaylandSurface::GetScale() parent scale %f", scale); + return scale; + } + + LOGVERBOSE("WaylandSurface::GetScale() no scale available"); + return sNoScale; +} + +void WaylandSurface::SetParentLocked(const WaylandSurfaceLock& aProofOfLock, + RefPtr aParent) { + mParent = aParent; +} + +} // namespace mozilla::widget diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build --- a/widget/gtk/moz.build +++ b/widget/gtk/moz.build @@ -31,11 +31,10 @@ if CONFIG["MOZ_ENABLE_V4L2"]: DIRS += ["v4l2test"] EXPORTS += [ "MozContainer.h", - "MozContainerSurfaceLock.h", "nsGTKToolkit.h", "nsGtkUtils.h", "nsImageToPixbuf.h", ] @@ -54,10 +53,11 @@ "DMABufSurface.h", "gbm.h", "GtkCompositorWidget.h", "InProcessGtkCompositorWidget.h", "va_drmcommon.h", + "WaylandSurfaceLock.h", "WindowSurface.h", "WindowSurfaceProvider.h", ] UNIFIED_SOURCES += [ @@ -70,11 +70,10 @@ "gtk3drawing.cpp", "GtkCompositorWidget.cpp", "IMContextWrapper.cpp", "InProcessGtkCompositorWidget.cpp", "MozContainer.cpp", - "MozContainerSurfaceLock.cpp", "MPRISServiceHandler.cpp", "NativeKeyBindings.cpp", "NativeMenuGtk.cpp", "NativeMenuSupport.cpp", "nsApplicationChooser.cpp", @@ -92,10 +91,11 @@ "nsUserIdleServiceGTK.cpp", "nsWidgetFactory.cpp", "ScreenHelperGTK.cpp", "TaskbarProgress.cpp", "WakeLockListener.cpp", + "WaylandSurfaceLock.cpp", "WidgetStyleCache.cpp", "WidgetUtilsGtk.cpp", "WindowSurfaceProvider.cpp", ] @@ -110,17 +110,18 @@ UNIFIED_SOURCES += [ "MozContainerWayland.cpp", "nsClipboardWayland.cpp", "nsWaylandDisplay.cpp", "WaylandBuffer.cpp", + "WaylandSurface.cpp", "WindowSurfaceWaylandMultiBuffer.cpp", ] EXPORTS.mozilla.widget += [ - "MozContainerSurfaceLock.h", "MozContainerWayland.h", "nsWaylandDisplay.h", "WaylandBuffer.h", + "WaylandSurface.h", ] if CONFIG["MOZ_X11"]: UNIFIED_SOURCES += [ "nsClipboardX11.cpp",