summaryrefslogtreecommitdiffstats
path: root/widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp417
1 files changed, 417 insertions, 0 deletions
diff --git a/widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp b/widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp
new file mode 100644
index 0000000000..31091f4b98
--- /dev/null
+++ b/widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp
@@ -0,0 +1,417 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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 "WindowSurfaceWaylandMultiBuffer.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <prenv.h>
+
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "MozContainer.h"
+#include "GtkCompositorWidget.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/WidgetUtils.h"
+
+#undef LOG
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetWaylandLog;
+# define LOGWAYLAND(...) \
+ MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#else
+# define LOGWAYLAND(...)
+#endif /* MOZ_LOGGING */
+
+namespace mozilla::widget {
+
+/*
+ Wayland multi-thread rendering scheme
+
+ Every rendering thread (main thread, compositor thread) contains its own
+ nsWaylandDisplay object connected to Wayland compositor (Mutter, Weston, etc.)
+
+ WindowSurfaceWayland implements WindowSurface class and draws nsWindow by
+ WindowSurface interface (Lock, Commit) to screen through nsWaylandDisplay.
+
+ ----------------------
+ | Wayland compositor |
+ ----------------------
+ ^
+ |
+ ----------------------
+ | nsWaylandDisplay |
+ ----------------------
+ ^ ^
+ | |
+ | |
+ | --------------------------------- ------------------
+ | | WindowSurfaceWayland |<------>| nsWindow |
+ | | | ------------------
+ | | ----------------------- |
+ | | | WaylandBufferSHM | |
+ | | | | |
+ | | | ------------------- | |
+ | | | | WaylandShmPool | | |
+ | | | ------------------- | |
+ | | ----------------------- |
+ | | |
+ | | ----------------------- |
+ | | | WaylandBufferSHM | |
+ | | | | |
+ | | | ------------------- | |
+ | | | | WaylandShmPool | | |
+ | | | ------------------- | |
+ | | ----------------------- |
+ | ---------------------------------
+ |
+ |
+ --------------------------------- ------------------
+ | WindowSurfaceWayland |<------>| nsWindow |
+ | | ------------------
+ | ----------------------- |
+ | | WaylandBufferSHM | |
+ | | | |
+ | | ------------------- | |
+ | | | WaylandShmPool | | |
+ | | ------------------- | |
+ | ----------------------- |
+ | |
+ | ----------------------- |
+ | | WaylandBufferSHM | |
+ | | | |
+ | | ------------------- | |
+ | | | WaylandShmPool | | |
+ | | ------------------- | |
+ | ----------------------- |
+ ---------------------------------
+
+
+nsWaylandDisplay
+
+Is our connection to Wayland display server,
+holds our display connection (wl_display) and event queue (wl_event_queue).
+
+nsWaylandDisplay is created for every thread which sends data to Wayland
+compositor. Wayland events for main thread is served by default Gtk+ loop,
+for other threads (compositor) we must create wl_event_queue and run event loop.
+
+
+WindowSurfaceWayland
+
+Is a Wayland implementation of WindowSurface class for WindowSurfaceProvider,
+we implement Lock() and Commit() interfaces from WindowSurface
+for actual drawing.
+
+One WindowSurfaceWayland draws one nsWindow so those are tied 1:1.
+At Wayland level it holds one wl_surface object.
+
+To perform visualiation of nsWindow, WindowSurfaceWayland contains one
+wl_surface and two wl_buffer objects (owned by WaylandBufferSHM)
+as we use double buffering. When nsWindow drawing is finished to wl_buffer,
+the wl_buffer is attached to wl_surface and it's sent to Wayland compositor.
+
+When there's no wl_buffer available for drawing (all wl_buffers are locked in
+compositor for instance) we store the drawing to WindowImageSurface object
+and draw later when wl_buffer becomes available or discard the
+WindowImageSurface cache when whole screen is invalidated.
+
+WaylandBufferSHM
+
+Is a class which provides a wl_buffer for drawing.
+Wl_buffer is a main Wayland object with actual graphics data.
+Wl_buffer basically represent one complete window screen.
+When double buffering is involved every window (GdkWindow for instance)
+utilises two wl_buffers which are cycled. One is filed with data by application
+and one is rendered by compositor.
+
+WaylandBufferSHM is implemented by shared memory (shm).
+It owns wl_buffer object, owns WaylandShmPool
+(which provides the shared memory) and ties them together.
+
+WaylandShmPool
+
+WaylandShmPool acts as a manager of shared memory for WaylandBufferSHM.
+Allocates it, holds reference to it and releases it.
+
+We allocate shared memory (shm) by mmap(..., MAP_SHARED,...) as an interface
+between us and wayland compositor. We draw our graphics data to the shm and
+handle to wayland compositor by WaylandBufferSHM/WindowSurfaceWayland
+(wl_buffer/wl_surface).
+*/
+
+using gfx::DataSourceSurface;
+
+#define BACK_BUFFER_NUM 3
+
+WindowSurfaceWaylandMB::WindowSurfaceWaylandMB(
+ RefPtr<nsWindow> aWindow, GtkCompositorWidget* aCompositorWidget)
+ : mSurfaceLock("WindowSurfaceWayland lock"),
+ mWindow(std::move(aWindow)),
+ mCompositorWidget(aCompositorWidget),
+ mFrameInProcess(false),
+ mCallbackRequested(false) {}
+
+bool WindowSurfaceWaylandMB::MaybeUpdateWindowSize() {
+ // We want to get window size from compositor widget as it matches window
+ // size used by parent RenderCompositorSWGL rendrer.
+ // For main thread rendering mCompositorWidget is not available so get
+ // window size directly from nsWindow.
+ LayoutDeviceIntSize newWindowSize = mCompositorWidget
+ ? mCompositorWidget->GetClientSize()
+ : mWindow->GetClientSize();
+ if (mWindowSize != newWindowSize) {
+ mWindowSize = newWindowSize;
+ return true;
+ }
+ return false;
+}
+
+already_AddRefed<DrawTarget> WindowSurfaceWaylandMB::Lock(
+ const LayoutDeviceIntRegion& aInvalidRegion) {
+ MutexAutoLock lock(mSurfaceLock);
+
+#ifdef MOZ_LOGGING
+ gfx::IntRect lockRect = aInvalidRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND("WindowSurfaceWaylandMB::Lock [%p] [%d,%d] -> [%d x %d] rects %d",
+ (void*)mWindow.get(), lockRect.x, lockRect.y, lockRect.width,
+ lockRect.height, aInvalidRegion.GetNumRects());
+#endif
+
+ if (mWindow->GetWindowType() == WindowType::Invisible) {
+ return nullptr;
+ }
+ mFrameInProcess = true;
+
+ CollectPendingSurfaces(lock);
+
+ if (MaybeUpdateWindowSize()) {
+ LOGWAYLAND(" new window size [%d x %d]", mWindowSize.width,
+ mWindowSize.height);
+ if (mInProgressBuffer) {
+ ReturnBufferToPool(lock, mInProgressBuffer);
+ mInProgressBuffer = nullptr;
+ }
+ if (mFrontBuffer) {
+ ReturnBufferToPool(lock, mFrontBuffer);
+ mFrontBuffer = nullptr;
+ }
+ mAvailableBuffers.Clear();
+ }
+
+ if (!mInProgressBuffer) {
+ if (mFrontBuffer && !mFrontBuffer->IsAttached()) {
+ mInProgressBuffer = mFrontBuffer;
+ } else {
+ mInProgressBuffer = ObtainBufferFromPool(lock, mWindowSize);
+ if (!mInProgressBuffer) {
+ return nullptr;
+ }
+ if (mFrontBuffer) {
+ HandlePartialUpdate(lock, aInvalidRegion);
+ ReturnBufferToPool(lock, mFrontBuffer);
+ }
+ }
+ mFrontBuffer = nullptr;
+ mFrontBufferInvalidRegion.SetEmpty();
+ }
+
+ RefPtr<DrawTarget> dt = mInProgressBuffer->Lock();
+ return dt.forget();
+}
+
+void WindowSurfaceWaylandMB::HandlePartialUpdate(
+ const MutexAutoLock& aProofOfLock,
+ const LayoutDeviceIntRegion& aInvalidRegion) {
+ LayoutDeviceIntRegion copyRegion;
+ if (mInProgressBuffer->GetBufferAge() == 2) {
+ copyRegion.Sub(mFrontBufferInvalidRegion, aInvalidRegion);
+ } else {
+ LayoutDeviceIntSize frontBufferSize = mFrontBuffer->GetSize();
+ copyRegion = LayoutDeviceIntRegion(LayoutDeviceIntRect(
+ 0, 0, frontBufferSize.width, frontBufferSize.height));
+ copyRegion.SubOut(aInvalidRegion);
+ }
+
+ if (!copyRegion.IsEmpty()) {
+ RefPtr<DataSourceSurface> dataSourceSurface =
+ mozilla::gfx::CreateDataSourceSurfaceFromData(
+ mFrontBuffer->GetSize().ToUnknownSize(),
+ mFrontBuffer->GetSurfaceFormat(),
+ (const uint8_t*)mFrontBuffer->GetShmPool()->GetImageData(),
+ mFrontBuffer->GetSize().width *
+ BytesPerPixel(mFrontBuffer->GetSurfaceFormat()));
+ RefPtr<DrawTarget> dt = mInProgressBuffer->Lock();
+
+ for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
+ LayoutDeviceIntRect r = iter.Get();
+ dt->CopySurface(dataSourceSurface, r.ToUnknownRect(),
+ gfx::IntPoint(r.x, r.y));
+ }
+ }
+}
+
+void WindowSurfaceWaylandMB::Commit(
+ const LayoutDeviceIntRegion& aInvalidRegion) {
+ MutexAutoLock lock(mSurfaceLock);
+ Commit(lock, aInvalidRegion);
+}
+
+void WindowSurfaceWaylandMB::Commit(
+ const MutexAutoLock& aProofOfLock,
+ const LayoutDeviceIntRegion& aInvalidRegion) {
+#ifdef MOZ_LOGGING
+ gfx::IntRect invalidRect = aInvalidRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND(
+ "WindowSurfaceWaylandMB::Commit [%p] damage rect [%d, %d] -> [%d x %d] "
+ "Window [%d x %d]\n",
+ (void*)mWindow.get(), invalidRect.x, invalidRect.y, invalidRect.width,
+ invalidRect.height, mWindowSize.width, mWindowSize.height);
+#endif
+
+ if (!mInProgressBuffer) {
+ // invisible window
+ return;
+ }
+ mFrameInProcess = false;
+
+ MozContainer* container = mWindow->GetMozContainer();
+ MozContainerSurfaceLock MozContainerLock(container);
+ struct wl_surface* waylandSurface = MozContainerLock.GetSurface();
+ if (!waylandSurface) {
+ LOGWAYLAND(
+ "WindowSurfaceWaylandMB::Commit [%p] frame queued: can't lock "
+ "wl_surface\n",
+ (void*)mWindow.get());
+ if (!mCallbackRequested) {
+ RefPtr<WindowSurfaceWaylandMB> self(this);
+ moz_container_wayland_add_initial_draw_callback_locked(
+ container, [self, aInvalidRegion]() -> void {
+ MutexAutoLock lock(self->mSurfaceLock);
+ if (!self->mFrameInProcess) {
+ self->Commit(lock, aInvalidRegion);
+ }
+ self->mCallbackRequested = false;
+ });
+ mCallbackRequested = true;
+ }
+ return;
+ }
+
+ if (moz_container_wayland_is_commiting_to_parent(container)) {
+ // 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(waylandSurface, 0, 0, INT32_MAX, INT32_MAX);
+ } else {
+ for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+ LayoutDeviceIntRect r = iter.Get();
+ wl_surface_damage_buffer(waylandSurface, r.x, r.y, r.width, r.height);
+ }
+ }
+
+ // aProofOfLock is a kind of substitution of MozContainerSurfaceLock.
+ // MozContainer is locked but MozContainerSurfaceLock doen't convert to
+ // MutexAutoLock& so we use aProofOfLock here.
+ moz_container_wayland_set_scale_factor_locked(aProofOfLock, container);
+
+ // It's possible that scale factor changed between Lock() and Commit()
+ // but window size is the same.
+ // Don't attach such buffer as it may have incorrect size,
+ // we'll paint new content soon.
+ if (moz_container_wayland_size_matches_scale_factor_locked(
+ aProofOfLock, container, mWindowSize.width, mWindowSize.height)) {
+ mInProgressBuffer->AttachAndCommit(waylandSurface);
+ }
+
+ mInProgressBuffer->ResetBufferAge();
+ mFrontBuffer = mInProgressBuffer;
+ mFrontBufferInvalidRegion = aInvalidRegion;
+ mInProgressBuffer = nullptr;
+
+ EnforcePoolSizeLimit(aProofOfLock);
+ IncrementBufferAge(aProofOfLock);
+
+ if (wl_display_flush(WaylandDisplayGet()->GetDisplay()) == -1) {
+ LOGWAYLAND("WindowSurfaceWaylandMB::Commit [%p] flush failed\n",
+ (void*)mWindow.get());
+ }
+}
+
+RefPtr<WaylandBufferSHM> WindowSurfaceWaylandMB::ObtainBufferFromPool(
+ const MutexAutoLock& aProofOfLock, const LayoutDeviceIntSize& aSize) {
+ if (!mAvailableBuffers.IsEmpty()) {
+ RefPtr<WaylandBufferSHM> buffer = mAvailableBuffers.PopLastElement();
+ mInUseBuffers.AppendElement(buffer);
+ return buffer;
+ }
+
+ RefPtr<WaylandBufferSHM> buffer = WaylandBufferSHM::Create(aSize);
+ if (buffer) {
+ mInUseBuffers.AppendElement(buffer);
+ }
+
+ return buffer;
+}
+
+void WindowSurfaceWaylandMB::ReturnBufferToPool(
+ const MutexAutoLock& aProofOfLock,
+ const RefPtr<WaylandBufferSHM>& aBuffer) {
+ if (aBuffer->IsAttached()) {
+ mPendingBuffers.AppendElement(aBuffer);
+ } else if (aBuffer->IsMatchingSize(mWindowSize)) {
+ mAvailableBuffers.AppendElement(aBuffer);
+ }
+ mInUseBuffers.RemoveElement(aBuffer);
+}
+
+void WindowSurfaceWaylandMB::EnforcePoolSizeLimit(
+ const MutexAutoLock& aProofOfLock) {
+ // Enforce the pool size limit, removing least-recently-used entries as
+ // necessary.
+ while (mAvailableBuffers.Length() > BACK_BUFFER_NUM) {
+ mAvailableBuffers.RemoveElementAt(0);
+ }
+
+ NS_WARNING_ASSERTION(mPendingBuffers.Length() < BACK_BUFFER_NUM,
+ "Are we leaking pending buffers?");
+ NS_WARNING_ASSERTION(mInUseBuffers.Length() < BACK_BUFFER_NUM,
+ "Are we leaking in-use buffers?");
+}
+
+void WindowSurfaceWaylandMB::CollectPendingSurfaces(
+ const MutexAutoLock& aProofOfLock) {
+ mPendingBuffers.RemoveElementsBy([&](auto& buffer) {
+ if (!buffer->IsAttached()) {
+ if (buffer->IsMatchingSize(mWindowSize)) {
+ mAvailableBuffers.AppendElement(std::move(buffer));
+ }
+ return true;
+ }
+ return false;
+ });
+}
+
+void WindowSurfaceWaylandMB::IncrementBufferAge(
+ const MutexAutoLock& aProofOfLock) {
+ for (const RefPtr<WaylandBufferSHM>& buffer : mInUseBuffers) {
+ buffer->IncrementBufferAge();
+ }
+ for (const RefPtr<WaylandBufferSHM>& buffer : mPendingBuffers) {
+ buffer->IncrementBufferAge();
+ }
+ for (const RefPtr<WaylandBufferSHM>& buffer : mAvailableBuffers) {
+ buffer->IncrementBufferAge();
+ }
+}
+
+} // namespace mozilla::widget