summaryrefslogtreecommitdiffstats
path: root/widget/gtk/WindowSurfaceWayland.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--widget/gtk/WindowSurfaceWayland.cpp1116
1 files changed, 1116 insertions, 0 deletions
diff --git a/widget/gtk/WindowSurfaceWayland.cpp b/widget/gtk/WindowSurfaceWayland.cpp
new file mode 100644
index 0000000000..2855ff7121
--- /dev/null
+++ b/widget/gtk/WindowSurfaceWayland.cpp
@@ -0,0 +1,1116 @@
+/* -*- 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 "nsWaylandDisplay.h"
+#include "WindowSurfaceWayland.h"
+
+#include "nsPrintfCString.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Tools.h"
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "MozContainer.h"
+#include "nsTArray.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_widget.h"
+
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#undef LOG
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetWaylandLog;
+# define LOGWAYLAND(args) \
+ MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOGWAYLAND(args)
+#endif /* MOZ_LOGGING */
+
+// Maximal compositing timeout it miliseconds
+#define COMPOSITING_TIMEOUT 200
+
+namespace mozilla {
+namespace 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 |
+ | | | ------------------
+ | | ----------------------- |
+ | | | WindowBackBuffer | |
+ | | | | |
+ | | | ------------------- | |
+ | | | | WaylandShmPool | | |
+ | | | ------------------- | |
+ | | ----------------------- |
+ | | |
+ | | ----------------------- |
+ | | | WindowBackBuffer | |
+ | | | | |
+ | | | ------------------- | |
+ | | | | WaylandShmPool | | |
+ | | | ------------------- | |
+ | | ----------------------- |
+ | ---------------------------------
+ |
+ |
+ --------------------------------- ------------------
+ | WindowSurfaceWayland |<------>| nsWindow |
+ | | ------------------
+ | ----------------------- |
+ | | WindowBackBuffer | |
+ | | | |
+ | | ------------------- | |
+ | | | WaylandShmPool | | |
+ | | ------------------- | |
+ | ----------------------- |
+ | |
+ | ----------------------- |
+ | | WindowBackBuffer | |
+ | | | |
+ | | ------------------- | |
+ | | | 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 WindowBackBuffer)
+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 availabe or discard the
+WindowImageSurface cache when whole screen is invalidated.
+
+WindowBackBuffer
+
+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.
+
+WindowBackBuffer 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 WindowBackBuffer.
+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 WindowBackBuffer/WindowSurfaceWayland
+(wl_buffer/wl_surface).
+*/
+
+#define EVENT_LOOP_DELAY (1000 / 240)
+
+#define BUFFER_BPP 4
+gfx::SurfaceFormat WindowBackBuffer::mFormat = gfx::SurfaceFormat::B8G8R8A8;
+
+int WindowBackBuffer::mDumpSerial =
+ PR_GetEnv("MOZ_WAYLAND_DUMP_WL_BUFFERS") ? 1 : 0;
+char* WindowBackBuffer::mDumpDir = PR_GetEnv("MOZ_WAYLAND_DUMP_DIR");
+
+RefPtr<nsWaylandDisplay> WindowBackBuffer::GetWaylandDisplay() {
+ return mWindowSurfaceWayland->GetWaylandDisplay();
+}
+
+static int WaylandAllocateShmMemory(int aSize) {
+ int fd = -1;
+ do {
+ static int counter = 0;
+ nsPrintfCString shmName("/wayland.mozilla.ipc.%d", counter++);
+ fd = shm_open(shmName.get(), O_CREAT | O_RDWR | O_EXCL, 0600);
+ if (fd >= 0) {
+ // We don't want to use leaked file
+ if (shm_unlink(shmName.get()) != 0) {
+ NS_WARNING("shm_unlink failed");
+ return -1;
+ }
+ }
+ } while (fd < 0 && errno == EEXIST);
+
+ if (fd < 0) {
+ NS_WARNING(nsPrintfCString("shm_open failed: %s", strerror(errno)).get());
+ return -1;
+ }
+
+ int ret = 0;
+#ifdef HAVE_POSIX_FALLOCATE
+ do {
+ ret = posix_fallocate(fd, 0, aSize);
+ } while (ret == EINTR);
+ if (ret != 0) {
+ NS_WARNING(
+ nsPrintfCString("posix_fallocate() fails to allocate shm memory: %s",
+ strerror(ret))
+ .get());
+ close(fd);
+ return -1;
+ }
+#else
+ do {
+ ret = ftruncate(fd, aSize);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0) {
+ NS_WARNING(nsPrintfCString("ftruncate() fails to allocate shm memory: %s",
+ strerror(ret))
+ .get());
+ close(fd);
+ fd = -1;
+ }
+#endif
+
+ return fd;
+}
+
+static bool WaylandReAllocateShmMemory(int aFd, int aSize) {
+ if (ftruncate(aFd, aSize) < 0) {
+ return false;
+ }
+#ifdef HAVE_POSIX_FALLOCATE
+ do {
+ errno = posix_fallocate(aFd, 0, aSize);
+ } while (errno == EINTR);
+ if (errno != 0) {
+ return false;
+ }
+#endif
+ return true;
+}
+
+WaylandShmPool::WaylandShmPool()
+ : mShmPool(nullptr),
+ mShmPoolFd(-1),
+ mAllocatedSize(0),
+ mImageData(MAP_FAILED){};
+
+void WaylandShmPool::Release() {
+ if (mImageData != MAP_FAILED) {
+ munmap(mImageData, mAllocatedSize);
+ mImageData = MAP_FAILED;
+ }
+ if (mShmPool) {
+ wl_shm_pool_destroy(mShmPool);
+ mShmPool = 0;
+ }
+ if (mShmPoolFd >= 0) {
+ close(mShmPoolFd);
+ mShmPoolFd = -1;
+ }
+}
+
+bool WaylandShmPool::Create(RefPtr<nsWaylandDisplay> aWaylandDisplay,
+ int aSize) {
+ // We do size increase only
+ if (aSize <= mAllocatedSize) {
+ return true;
+ }
+
+ if (mShmPoolFd < 0) {
+ mShmPoolFd = WaylandAllocateShmMemory(aSize);
+ if (mShmPoolFd < 0) {
+ return false;
+ }
+ } else {
+ if (!WaylandReAllocateShmMemory(mShmPoolFd, aSize)) {
+ Release();
+ return false;
+ }
+ }
+
+ if (mImageData != MAP_FAILED) {
+ munmap(mImageData, mAllocatedSize);
+ }
+ mImageData =
+ mmap(nullptr, aSize, PROT_READ | PROT_WRITE, MAP_SHARED, mShmPoolFd, 0);
+ if (mImageData == MAP_FAILED) {
+ NS_WARNING("Unable to map drawing surface!");
+ Release();
+ return false;
+ }
+
+ if (mShmPool) {
+ wl_shm_pool_resize(mShmPool, aSize);
+ } else {
+ mShmPool = wl_shm_create_pool(aWaylandDisplay->GetShm(), mShmPoolFd, aSize);
+ // We set our queue to get mShmPool events at compositor thread.
+ wl_proxy_set_queue((struct wl_proxy*)mShmPool,
+ aWaylandDisplay->GetEventQueue());
+ }
+
+ mAllocatedSize = aSize;
+ return true;
+}
+
+void WaylandShmPool::SetImageDataFromPool(class WaylandShmPool* aSourcePool,
+ int aImageDataSize) {
+ MOZ_ASSERT(mAllocatedSize >= aImageDataSize, "WaylandShmPool overflows!");
+ memcpy(mImageData, aSourcePool->GetImageData(), aImageDataSize);
+}
+
+WaylandShmPool::~WaylandShmPool() { Release(); }
+
+static void buffer_release(void* data, wl_buffer* buffer) {
+ auto surface = reinterpret_cast<WindowBackBuffer*>(data);
+ surface->Detach(buffer);
+}
+
+static const struct wl_buffer_listener buffer_listener = {buffer_release};
+
+bool WindowBackBuffer::Create(int aWidth, int aHeight) {
+ MOZ_ASSERT(!IsAttached(), "We can't create attached buffers.");
+
+ ReleaseWLBuffer();
+
+ int size = aWidth * aHeight * BUFFER_BPP;
+ if (!mShmPool.Create(GetWaylandDisplay(), size)) {
+ return false;
+ }
+
+ mWLBuffer =
+ wl_shm_pool_create_buffer(mShmPool.GetShmPool(), 0, aWidth, aHeight,
+ aWidth * BUFFER_BPP, WL_SHM_FORMAT_ARGB8888);
+ wl_proxy_set_queue((struct wl_proxy*)mWLBuffer,
+ GetWaylandDisplay()->GetEventQueue());
+ wl_buffer_add_listener(mWLBuffer, &buffer_listener, this);
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+
+ LOGWAYLAND(("WindowBackBuffer::Create [%p] wl_buffer %p ID %d\n", (void*)this,
+ (void*)mWLBuffer,
+ mWLBuffer ? wl_proxy_get_id((struct wl_proxy*)mWLBuffer) : -1));
+ return true;
+}
+
+void WindowBackBuffer::ReleaseWLBuffer() {
+ LOGWAYLAND(("WindowBackBuffer::Release [%p]\n", (void*)this));
+ if (mWLBuffer) {
+ wl_buffer_destroy(mWLBuffer);
+ mWLBuffer = nullptr;
+ }
+ mWidth = mHeight = 0;
+}
+
+void WindowBackBuffer::Clear() {
+ memset(mShmPool.GetImageData(), 0, mHeight * mWidth * BUFFER_BPP);
+}
+
+WindowBackBuffer::WindowBackBuffer(WindowSurfaceWayland* aWindowSurfaceWayland)
+ : mWindowSurfaceWayland(aWindowSurfaceWayland),
+ mShmPool(),
+ mWLBuffer(nullptr),
+ mWidth(0),
+ mHeight(0),
+ mAttached(false) {}
+
+WindowBackBuffer::~WindowBackBuffer() { ReleaseWLBuffer(); }
+
+bool WindowBackBuffer::Resize(int aWidth, int aHeight) {
+ if (aWidth == mWidth && aHeight == mHeight) {
+ return true;
+ }
+ LOGWAYLAND(
+ ("WindowBackBuffer::Resize [%p] %d %d\n", (void*)this, aWidth, aHeight));
+ Create(aWidth, aHeight);
+ return (mWLBuffer != nullptr);
+}
+
+void WindowBackBuffer::Attach(wl_surface* aSurface) {
+ LOGWAYLAND(
+ ("WindowBackBuffer::Attach [%p] wl_surface %p ID %d wl_buffer %p ID %d\n",
+ (void*)this, (void*)aSurface,
+ aSurface ? wl_proxy_get_id((struct wl_proxy*)aSurface) : -1,
+ (void*)GetWlBuffer(),
+ GetWlBuffer() ? wl_proxy_get_id((struct wl_proxy*)GetWlBuffer()) : -1));
+
+ wl_buffer* buffer = GetWlBuffer();
+ if (buffer) {
+ mAttached = true;
+ wl_surface_attach(aSurface, buffer, 0, 0);
+ wl_surface_commit(aSurface);
+ wl_display_flush(WaylandDisplayGetWLDisplay());
+ }
+}
+
+void WindowBackBuffer::Detach(wl_buffer* aBuffer) {
+ LOGWAYLAND(("WindowBackBuffer::Detach [%p] wl_buffer %p ID %d\n", (void*)this,
+ (void*)aBuffer,
+ aBuffer ? wl_proxy_get_id((struct wl_proxy*)aBuffer) : -1));
+ mAttached = false;
+
+ // Commit any potential cached drawings from latest Lock()/Commit() cycle.
+ mWindowSurfaceWayland->FlushPendingCommits();
+}
+
+bool WindowBackBuffer::SetImageDataFromBuffer(
+ class WindowBackBuffer* aSourceBuffer) {
+ auto sourceBuffer = static_cast<class WindowBackBuffer*>(aSourceBuffer);
+ if (!IsMatchingSize(sourceBuffer)) {
+ if (!Resize(sourceBuffer->mWidth, sourceBuffer->mHeight)) {
+ return false;
+ }
+ }
+
+ mShmPool.SetImageDataFromPool(
+ &sourceBuffer->mShmPool,
+ sourceBuffer->mWidth * sourceBuffer->mHeight * BUFFER_BPP);
+ return true;
+}
+
+already_AddRefed<gfx::DrawTarget> WindowBackBuffer::Lock() {
+ LOGWAYLAND(("WindowBackBuffer::Lock [%p] [%d x %d] wl_buffer %p ID %d\n",
+ (void*)this, mWidth, mHeight, (void*)mWLBuffer,
+ mWLBuffer ? wl_proxy_get_id((struct wl_proxy*)mWLBuffer) : -1));
+
+ gfx::IntSize lockSize(mWidth, mHeight);
+ mIsLocked = true;
+ return gfxPlatform::CreateDrawTargetForData(
+ static_cast<unsigned char*>(mShmPool.GetImageData()), lockSize,
+ BUFFER_BPP * mWidth, GetSurfaceFormat());
+}
+
+#ifdef MOZ_LOGGING
+void WindowBackBuffer::DumpToFile(const char* aHint) {
+ if (!mDumpSerial) {
+ return;
+ }
+
+ cairo_surface_t* surface = nullptr;
+ auto unmap = MakeScopeExit([&] {
+ if (surface) {
+ cairo_surface_destroy(surface);
+ }
+ });
+ surface = cairo_image_surface_create_for_data(
+ (unsigned char*)mShmPool.GetImageData(), CAIRO_FORMAT_ARGB32, mWidth,
+ mHeight, BUFFER_BPP * mWidth);
+ if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
+ nsCString filename;
+ if (mDumpDir) {
+ filename.Append(mDumpDir);
+ filename.Append('/');
+ }
+ filename.Append(
+ nsPrintfCString("firefox-wl-buffer-%.5d-%s.png", mDumpSerial++, aHint));
+ cairo_surface_write_to_png(surface, filename.get());
+ LOGWAYLAND(("Dumped wl_buffer to %s\n", filename.get()));
+ }
+}
+#endif
+
+static void frame_callback_handler(void* data, struct wl_callback* callback,
+ uint32_t time) {
+ auto surface = reinterpret_cast<WindowSurfaceWayland*>(data);
+ surface->FrameCallbackHandler();
+}
+
+static const struct wl_callback_listener frame_listener = {
+ frame_callback_handler};
+
+WindowSurfaceWayland::WindowSurfaceWayland(nsWindow* aWindow)
+ : mWindow(aWindow),
+ mWaylandDisplay(WaylandDisplayGet()),
+ mWaylandBuffer(nullptr),
+ mWaylandFullscreenDamage(false),
+ mFrameCallback(nullptr),
+ mLastCommittedSurface(nullptr),
+ mLastCommitTime(0),
+ mDrawToWaylandBufferDirectly(true),
+ mCanSwitchWaylandBuffer(true),
+ mBufferPendingCommit(false),
+ mBufferCommitAllowed(false),
+ mBufferNeedsClear(false),
+ mSmoothRendering(StaticPrefs::widget_wayland_smooth_rendering()),
+ mSurfaceReadyTimerID(),
+ mSurfaceLock("WindowSurfaceWayland lock") {
+ for (int i = 0; i < BACK_BUFFER_NUM; i++) {
+ mShmBackupBuffer[i] = nullptr;
+ }
+}
+
+WindowSurfaceWayland::~WindowSurfaceWayland() {
+ MutexAutoLock lock(mSurfaceLock);
+
+ if (mSurfaceReadyTimerID) {
+ g_source_remove(mSurfaceReadyTimerID);
+ mSurfaceReadyTimerID = 0;
+ }
+
+ if (mBufferPendingCommit) {
+ NS_WARNING("Deleted WindowSurfaceWayland with a pending commit!");
+ }
+
+ if (mFrameCallback) {
+ wl_callback_destroy(mFrameCallback);
+ }
+
+ mWaylandBuffer = nullptr;
+
+ for (int i = 0; i < BACK_BUFFER_NUM; i++) {
+ if (mShmBackupBuffer[i]) {
+ delete mShmBackupBuffer[i];
+ }
+ }
+}
+
+WindowBackBuffer* WindowSurfaceWayland::CreateWaylandBuffer(int aWidth,
+ int aHeight) {
+ int availableBuffer;
+ for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
+ availableBuffer++) {
+ if (!mShmBackupBuffer[availableBuffer]) {
+ break;
+ }
+ }
+
+ // There isn't any free slot for additional buffer.
+ if (availableBuffer == BACK_BUFFER_NUM) {
+ return nullptr;
+ }
+
+ WindowBackBuffer* buffer = new WindowBackBuffer(this);
+ if (!buffer->Create(aWidth, aHeight)) {
+ delete buffer;
+ return nullptr;
+ }
+
+ mShmBackupBuffer[availableBuffer] = buffer;
+ return buffer;
+}
+
+WindowBackBuffer* WindowSurfaceWayland::WaylandBufferFindAvailable(
+ int aWidth, int aHeight) {
+ int availableBuffer;
+ // Try to find a buffer which matches the size
+ for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
+ availableBuffer++) {
+ WindowBackBuffer* buffer = mShmBackupBuffer[availableBuffer];
+ if (buffer && !buffer->IsAttached() &&
+ buffer->IsMatchingSize(aWidth, aHeight)) {
+ return buffer;
+ }
+ }
+
+ // Try to find any buffer
+ for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
+ availableBuffer++) {
+ WindowBackBuffer* buffer = mShmBackupBuffer[availableBuffer];
+ if (buffer && !buffer->IsAttached()) {
+ return buffer;
+ }
+ }
+
+ return nullptr;
+}
+
+WindowBackBuffer* WindowSurfaceWayland::SetNewWaylandBuffer() {
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::NewWaylandBuffer [%p] Requested buffer [%d "
+ "x %d]\n",
+ (void*)this, mWLBufferRect.width, mWLBufferRect.height));
+
+ mWaylandBuffer =
+ WaylandBufferFindAvailable(mWLBufferRect.width, mWLBufferRect.height);
+ if (mWaylandBuffer) {
+ if (!mWaylandBuffer->Resize(mWLBufferRect.width, mWLBufferRect.height)) {
+ return nullptr;
+ }
+ return mWaylandBuffer;
+ }
+
+ mWaylandBuffer =
+ CreateWaylandBuffer(mWLBufferRect.width, mWLBufferRect.height);
+ return mWaylandBuffer;
+}
+
+// Recent
+WindowBackBuffer* WindowSurfaceWayland::GetWaylandBuffer() {
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::GetWaylandBuffer [%p] Requested buffer [%d "
+ "x %d] can switch %d\n",
+ (void*)this, mWLBufferRect.width, mWLBufferRect.height,
+ mCanSwitchWaylandBuffer));
+
+ // There's no buffer created yet, create a new one for partial screen updates.
+ if (!mWaylandBuffer) {
+ return SetNewWaylandBuffer();
+ }
+
+ if (mWaylandBuffer->IsAttached()) {
+ if (mCanSwitchWaylandBuffer) {
+ return SetNewWaylandBuffer();
+ }
+ LOGWAYLAND((" Buffer is attached and we can't switch, return null\n"));
+ return nullptr;
+ }
+
+ if (mWaylandBuffer->IsMatchingSize(mWLBufferRect.width,
+ mWLBufferRect.height)) {
+ LOGWAYLAND((" Size is ok, use the buffer [%d x %d]\n",
+ mWLBufferRect.width, mWLBufferRect.height));
+ return mWaylandBuffer;
+ }
+
+ if (mCanSwitchWaylandBuffer) {
+ // Reuse existing buffer
+ LOGWAYLAND((" Reuse buffer with resize [%d x %d]\n", mWLBufferRect.width,
+ mWLBufferRect.height));
+ if (mWaylandBuffer->Resize(mWLBufferRect.width, mWLBufferRect.height)) {
+ return mWaylandBuffer;
+ }
+ // OOM here, just return null to skip this frame.
+ return nullptr;
+ }
+
+ LOGWAYLAND(
+ (" Buffer size does not match, requested %d x %d got %d x%d, return "
+ "null.\n",
+ mWaylandBuffer->GetWidth(), mWaylandBuffer->GetHeight(),
+ mWLBufferRect.width, mWLBufferRect.height));
+ return nullptr;
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::LockWaylandBuffer() {
+ // Allocated wayland buffer can't be bigger than mozilla widget size.
+ LayoutDeviceIntRegion region;
+ region.And(mLockedScreenRect, mWindow->GetMozContainerSize());
+ mWLBufferRect = LayoutDeviceIntRect(region.GetBounds());
+
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::LockWaylandBuffer [%p] Requesting buffer %d x "
+ "%d\n",
+ (void*)this, mWLBufferRect.width, mWLBufferRect.height));
+
+ WindowBackBuffer* buffer = GetWaylandBuffer();
+ LOGWAYLAND(("WindowSurfaceWayland::LockWaylandBuffer [%p] Got buffer %p\n",
+ (void*)this, (void*)buffer));
+
+ if (!buffer) {
+ if (mLastCommitTime && (g_get_monotonic_time() / 1000) - mLastCommitTime >
+ COMPOSITING_TIMEOUT) {
+ NS_WARNING(
+ "Slow response from Wayland compositor, visual glitches ahead.");
+ }
+ return nullptr;
+ }
+
+ mCanSwitchWaylandBuffer = false;
+
+ if (mBufferNeedsClear) {
+ buffer->Clear();
+ mBufferNeedsClear = false;
+ }
+
+ return buffer->Lock();
+}
+
+void WindowSurfaceWayland::UnlockWaylandBuffer() {
+ LOGWAYLAND(("WindowSurfaceWayland::UnlockWaylandBuffer [%p]\n", (void*)this));
+ mWaylandBuffer->Unlock();
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::LockImageSurface(
+ const gfx::IntSize& aLockSize) {
+ if (!mImageSurface || !(aLockSize <= mImageSurface->GetSize())) {
+ mImageSurface = gfx::Factory::CreateDataSourceSurface(
+ aLockSize, WindowBackBuffer::GetSurfaceFormat());
+ }
+ gfx::DataSourceSurface::MappedSurface map = {nullptr, 0};
+ if (!mImageSurface->Map(gfx::DataSourceSurface::READ_WRITE, &map)) {
+ return nullptr;
+ }
+ return gfxPlatform::CreateDrawTargetForData(
+ map.mData, mImageSurface->GetSize(), map.mStride,
+ WindowBackBuffer::GetSurfaceFormat());
+}
+
+static bool IsWindowFullScreenUpdate(
+ LayoutDeviceIntRect& aScreenRect,
+ const LayoutDeviceIntRegion& aUpdatedRegion) {
+ if (aUpdatedRegion.GetNumRects() > 1) return false;
+
+ gfx::IntRect rect = aUpdatedRegion.RectIter().Get().ToUnknownRect();
+ return (rect.x == 0 && rect.y == 0 && aScreenRect.width == rect.width &&
+ aScreenRect.height == rect.height);
+}
+
+static bool IsPopupFullScreenUpdate(
+ LayoutDeviceIntRect& aScreenRect,
+ const LayoutDeviceIntRegion& aUpdatedRegion) {
+ // We know that popups can be drawn from two parts; a panel and an arrow.
+ // Assume we redraw whole popups when we have two rects and bounding
+ // box is equal to window borders.
+ if (aUpdatedRegion.GetNumRects() > 2) return false;
+
+ gfx::IntRect lockSize = aUpdatedRegion.GetBounds().ToUnknownRect();
+ return (lockSize.x == 0 && lockSize.y == 0 &&
+ aScreenRect.width == lockSize.width &&
+ aScreenRect.height == lockSize.height);
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::Lock(
+ const LayoutDeviceIntRegion& aRegion) {
+ if (mWindow->WindowType() == eWindowType_invisible) {
+ return nullptr;
+ }
+
+ // Wait until all pending events are processed. There may be queued
+ // wl_buffer release event which releases our wl_buffer for further rendering.
+ mWaylandDisplay->WaitForSyncEnd();
+
+ // Lock the surface *after* WaitForSyncEnd() call as is can fire
+ // FlushPendingCommits().
+ MutexAutoLock lock(mSurfaceLock);
+
+ // Disable all commits (from potential frame callback/delayed handlers)
+ // until next WindowSurfaceWayland::Commit() call.
+ mBufferCommitAllowed = false;
+
+ LayoutDeviceIntRect lockedScreenRect = mWindow->GetBounds();
+ // The window bounds of popup windows contains relative position to
+ // the transient window. We need to remove that effect because by changing
+ // position of the popup window the buffer has not changed its size.
+ lockedScreenRect.x = lockedScreenRect.y = 0;
+ gfx::IntRect lockSize = aRegion.GetBounds().ToUnknownRect();
+
+ bool isTransparentPopup =
+ mWindow->IsWaylandPopup() &&
+ (eTransparencyTransparent == mWindow->GetTransparencyMode());
+
+ bool windowRedraw = isTransparentPopup
+ ? IsPopupFullScreenUpdate(lockedScreenRect, aRegion)
+ : IsWindowFullScreenUpdate(lockedScreenRect, aRegion);
+ if (windowRedraw) {
+ // Clear buffer when we (re)draw new transparent popup window,
+ // otherwise leave it as-is, mBufferNeedsClear can be set from previous
+ // (already pending) commits which are cached now.
+ mBufferNeedsClear =
+ mWindow->WaylandSurfaceNeedsClear() || isTransparentPopup;
+
+ // Store info that we can switch WaylandBuffer when we flush
+ // mImageSurface / mDelayedImageCommits. Don't clear it - it's cleared
+ // at LockWaylandBuffer() when we actualy switch the buffer.
+ mCanSwitchWaylandBuffer = true;
+
+ // We do full buffer repaint so clear our cached drawings.
+ mDelayedImageCommits.Clear();
+ mWaylandBufferDamage.SetEmpty();
+
+ // Store info that we can safely invalidate whole screen.
+ mWaylandFullscreenDamage = true;
+ } else {
+ // We can switch buffer if there isn't any content committed
+ // to active buffer.
+ mCanSwitchWaylandBuffer = !mBufferPendingCommit;
+ }
+
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::Lock [%p] [%d,%d] -> [%d x %d] rects %d "
+ "windowSize [%d x %d]\n",
+ (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
+ aRegion.GetNumRects(), lockedScreenRect.width, lockedScreenRect.height));
+ LOGWAYLAND((" nsWindow = %p\n", mWindow));
+ LOGWAYLAND((" isPopup = %d\n", mWindow->IsWaylandPopup()));
+ LOGWAYLAND((" isTransparentPopup = %d\n", isTransparentPopup));
+ LOGWAYLAND((" IsPopupFullScreenUpdate = %d\n",
+ IsPopupFullScreenUpdate(lockedScreenRect, aRegion)));
+ LOGWAYLAND((" IsWindowFullScreenUpdate = %d\n",
+ IsWindowFullScreenUpdate(lockedScreenRect, aRegion)));
+ LOGWAYLAND((" mBufferNeedsClear = %d\n", mBufferNeedsClear));
+ LOGWAYLAND((" mBufferPendingCommit = %d\n", mBufferPendingCommit));
+ LOGWAYLAND((" mCanSwitchWaylandBuffer = %d\n", mCanSwitchWaylandBuffer));
+ LOGWAYLAND((" windowRedraw = %d\n", windowRedraw));
+
+ if (!(mLockedScreenRect == lockedScreenRect)) {
+ LOGWAYLAND((" screen size changed\n"));
+
+ // Screen (window) size changed and we still have some painting pending
+ // for the last window size. That can happen when window is resized.
+ // We can't commit them any more as they're for former window size, so
+ // scratch them.
+ mDelayedImageCommits.Clear();
+ mWaylandBufferDamage.SetEmpty();
+
+ if (!windowRedraw) {
+ NS_WARNING("Partial screen update when window is resized!");
+ // This should not happen. Screen size changed but we got only
+ // partal screen update instead of whole screen. Discard this painting
+ // as it produces artifacts.
+ return nullptr;
+ }
+ mLockedScreenRect = lockedScreenRect;
+ }
+
+ // We can draw directly only when widget has the same size as wl_buffer
+ LayoutDeviceIntRect size = mWindow->GetMozContainerSize();
+ mDrawToWaylandBufferDirectly = (size.width >= mLockedScreenRect.width &&
+ size.height >= mLockedScreenRect.height);
+
+ // We can draw directly only when we redraw significant part of the window
+ // to avoid flickering or do only fullscreen updates in smooth mode.
+ if (mDrawToWaylandBufferDirectly) {
+ mDrawToWaylandBufferDirectly =
+ mSmoothRendering
+ ? windowRedraw
+ : (windowRedraw || (lockSize.width * 2 > lockedScreenRect.width &&
+ lockSize.height * 2 > lockedScreenRect.height));
+ }
+ if (!mDrawToWaylandBufferDirectly) {
+ // Don't switch wl_buffers when we cache drawings.
+ mCanSwitchWaylandBuffer = false;
+ LOGWAYLAND((" Indirect drawing, mCanSwitchWaylandBuffer = %d\n",
+ mCanSwitchWaylandBuffer));
+ }
+
+ if (mDrawToWaylandBufferDirectly) {
+ LOGWAYLAND((" Direct drawing\n"));
+ RefPtr<gfx::DrawTarget> dt = LockWaylandBuffer();
+ if (dt) {
+#if MOZ_LOGGING
+ mWaylandBuffer->DumpToFile("Lock");
+#endif
+ if (!windowRedraw) {
+ DrawDelayedImageCommits(dt, mWaylandBufferDamage);
+#if MOZ_LOGGING
+ mWaylandBuffer->DumpToFile("Lock-after-commit");
+#endif
+ }
+ mBufferPendingCommit = true;
+ return dt.forget();
+ }
+ }
+
+ // We do indirect drawing because there isn't any front buffer available.
+ // Do indirect drawing to mImageSurface which is commited to wayland
+ // wl_buffer by DrawDelayedImageCommits() later.
+ mDrawToWaylandBufferDirectly = false;
+
+ LOGWAYLAND((" Indirect drawing.\n"));
+ return LockImageSurface(gfx::IntSize(lockSize.XMost(), lockSize.YMost()));
+}
+
+bool WindowImageSurface::OverlapsSurface(
+ class WindowImageSurface& aBottomSurface) {
+ return mUpdateRegion.Contains(aBottomSurface.mUpdateRegion);
+}
+
+void WindowImageSurface::DrawToTarget(
+ gfx::DrawTarget* aDest, LayoutDeviceIntRegion& aWaylandBufferDamage) {
+#ifdef MOZ_LOGGING
+ gfx::IntRect bounds = mUpdateRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND(("WindowImageSurface::DrawToTarget\n"));
+ LOGWAYLAND((" rects num %d\n", mUpdateRegion.GetNumRects()));
+ LOGWAYLAND((" bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
+ bounds.width, bounds.height));
+#endif
+ for (auto iter = mUpdateRegion.RectIter(); !iter.Done(); iter.Next()) {
+ gfx::IntRect r(iter.Get().ToUnknownRect());
+ LOGWAYLAND(
+ (" draw rect [%d,%d] -> [%d x %d]\n", r.x, r.y, r.width, r.height));
+ aDest->CopySurface(mImageSurface, r, gfx::IntPoint(r.x, r.y));
+ }
+ aWaylandBufferDamage.OrWith(mUpdateRegion);
+}
+
+WindowImageSurface::WindowImageSurface(
+ gfx::DataSourceSurface* aImageSurface,
+ const LayoutDeviceIntRegion& aUpdateRegion)
+ : mImageSurface(aImageSurface), mUpdateRegion(aUpdateRegion) {}
+
+void WindowSurfaceWayland::DrawDelayedImageCommits(
+ gfx::DrawTarget* aDrawTarget, LayoutDeviceIntRegion& aWaylandBufferDamage) {
+ unsigned int imagesNum = mDelayedImageCommits.Length();
+ LOGWAYLAND(("WindowSurfaceWayland::DrawDelayedImageCommits [%p] len %d\n",
+ (void*)this, imagesNum));
+ for (unsigned int i = 0; i < imagesNum; i++) {
+ mDelayedImageCommits[i].DrawToTarget(aDrawTarget, aWaylandBufferDamage);
+ }
+ mDelayedImageCommits.Clear();
+}
+
+void WindowSurfaceWayland::CacheImageSurface(
+ const LayoutDeviceIntRegion& aRegion) {
+#ifdef MOZ_LOGGING
+ gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND(("WindowSurfaceWayland::CacheImageSurface [%p]\n", (void*)this));
+ LOGWAYLAND((" rects num %d\n", aRegion.GetNumRects()));
+ LOGWAYLAND((" bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
+ bounds.width, bounds.height));
+#endif
+
+ mImageSurface->Unmap();
+ WindowImageSurface surf = WindowImageSurface(mImageSurface, aRegion);
+
+ if (mDelayedImageCommits.Length()) {
+ auto lastSurf = mDelayedImageCommits.PopLastElement();
+ if (surf.OverlapsSurface(lastSurf)) {
+#ifdef MOZ_LOGGING
+ {
+ gfx::IntRect size =
+ lastSurf.GetUpdateRegion()->GetBounds().ToUnknownRect();
+ LOGWAYLAND((" removing [ %d, %d] -> [%d x %d]\n", size.x, size.y,
+ size.width, size.height));
+ }
+#endif
+ } else {
+ mDelayedImageCommits.AppendElement(lastSurf);
+ }
+ }
+
+ mDelayedImageCommits.AppendElement(surf);
+ // mImageSurface is owned by mDelayedImageCommits
+ mImageSurface = nullptr;
+
+ LOGWAYLAND(
+ (" There's %d cached images\n", int(mDelayedImageCommits.Length())));
+}
+
+bool WindowSurfaceWayland::CommitImageCacheToWaylandBuffer() {
+ if (!mDelayedImageCommits.Length()) {
+ return false;
+ }
+
+ MOZ_ASSERT(!mDrawToWaylandBufferDirectly);
+
+ RefPtr<gfx::DrawTarget> dt = LockWaylandBuffer();
+ if (!dt) {
+ return false;
+ }
+
+ LOGWAYLAND((" Flushing %ld cached WindowImageSurfaces to Wayland buffer\n",
+ long(mDelayedImageCommits.Length())));
+
+ DrawDelayedImageCommits(dt, mWaylandBufferDamage);
+ UnlockWaylandBuffer();
+
+ return true;
+}
+
+void WindowSurfaceWayland::FlushPendingCommits() {
+ MutexAutoLock lock(mSurfaceLock);
+ if (FlushPendingCommitsLocked()) {
+ mWaylandDisplay->QueueSyncBegin();
+ }
+}
+
+// When a new window is created we may not have a valid wl_surface
+// for drawing (Gtk haven't created it yet). All commits are queued
+// and FlushPendingCommitsLocked() is called by timer when wl_surface is ready
+// for drawing.
+static int WaylandBufferFlushPendingCommits(void* data) {
+ WindowSurfaceWayland* aSurface = static_cast<WindowSurfaceWayland*>(data);
+ aSurface->FlushPendingCommits();
+ return true;
+}
+
+bool WindowSurfaceWayland::FlushPendingCommitsLocked() {
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::FlushPendingCommitsLocked [%p]\n", (void*)this));
+ LOGWAYLAND(
+ (" mDrawToWaylandBufferDirectly = %d\n", mDrawToWaylandBufferDirectly));
+ LOGWAYLAND((" mCanSwitchWaylandBuffer = %d\n", mCanSwitchWaylandBuffer));
+ LOGWAYLAND((" mFrameCallback = %p\n", mFrameCallback));
+ LOGWAYLAND((" mLastCommittedSurface = %p\n", mLastCommittedSurface));
+ LOGWAYLAND((" mBufferPendingCommit = %d\n", mBufferPendingCommit));
+ LOGWAYLAND((" mBufferCommitAllowed = %d\n", mBufferCommitAllowed));
+
+ if (!mBufferCommitAllowed) {
+ return false;
+ }
+
+ if (CommitImageCacheToWaylandBuffer()) {
+ mBufferPendingCommit = true;
+ }
+
+ // There's nothing to do here
+ if (!mBufferPendingCommit) {
+ return false;
+ }
+
+ MOZ_ASSERT(!mWaylandBuffer->IsAttached(),
+ "We can't draw to attached wayland buffer!");
+
+ MozContainer* container = mWindow->GetMozContainer();
+ wl_surface* waylandSurface = moz_container_wayland_surface_lock(container);
+ if (!waylandSurface) {
+ LOGWAYLAND((" [%p] mWindow->GetWaylandSurface() failed, delay commit.\n",
+ (void*)this));
+
+ // Target window is not created yet - delay the commit. This can happen only
+ // when the window is newly created and there's no active
+ // frame callback pending.
+ MOZ_ASSERT(!mFrameCallback || waylandSurface != mLastCommittedSurface,
+ "Missing wayland surface at frame callback!");
+
+ if (!mSurfaceReadyTimerID) {
+ mSurfaceReadyTimerID = g_timeout_add(
+ EVENT_LOOP_DELAY, &WaylandBufferFlushPendingCommits, this);
+ }
+ return true;
+ }
+ if (mSurfaceReadyTimerID) {
+ g_source_remove(mSurfaceReadyTimerID);
+ mSurfaceReadyTimerID = 0;
+ }
+
+ auto unlockContainer = MakeScopeExit([&] {
+ moz_container_wayland_surface_unlock(container, &waylandSurface);
+ });
+
+ wl_proxy_set_queue((struct wl_proxy*)waylandSurface,
+ mWaylandDisplay->GetEventQueue());
+
+ // We have an active frame callback request so handle it.
+ if (mFrameCallback) {
+ if (waylandSurface == mLastCommittedSurface) {
+ LOGWAYLAND((" [%p] wait for frame callback.\n", (void*)this));
+ // We have an active frame callback pending from our recent surface.
+ // It means we should defer the commit to FrameCallbackHandler().
+ return true;
+ }
+ // If our stored wl_surface does not match the actual one it means the frame
+ // callback is no longer active and we should release it.
+ wl_callback_destroy(mFrameCallback);
+ mFrameCallback = nullptr;
+ mLastCommittedSurface = nullptr;
+ }
+
+ if (mWaylandFullscreenDamage) {
+ LOGWAYLAND((" wl_surface_damage full screen\n"));
+ wl_surface_damage(waylandSurface, 0, 0, INT_MAX, INT_MAX);
+ } else {
+ for (auto iter = mWaylandBufferDamage.RectIter(); !iter.Done();
+ iter.Next()) {
+ mozilla::LayoutDeviceIntRect r = iter.Get();
+ LOGWAYLAND((" wl_surface_damage_buffer [%d, %d] -> [%d, %d]\n", r.x,
+ r.y, r.width, r.height));
+ wl_surface_damage_buffer(waylandSurface, r.x, r.y, r.width, r.height);
+ }
+ }
+
+#if MOZ_LOGGING
+ mWaylandBuffer->DumpToFile("Commit");
+#endif
+
+ // Clear all back buffer damage as we're committing
+ // all requested regions.
+ mWaylandFullscreenDamage = false;
+ mWaylandBufferDamage.SetEmpty();
+
+ mFrameCallback = wl_surface_frame(waylandSurface);
+ wl_callback_add_listener(mFrameCallback, &frame_listener, this);
+
+ mWaylandBuffer->Attach(waylandSurface);
+ mLastCommittedSurface = waylandSurface;
+ mLastCommitTime = g_get_monotonic_time() / 1000;
+
+ // There's no pending commit, all changes are sent to compositor.
+ mBufferPendingCommit = false;
+
+ return true;
+}
+
+void WindowSurfaceWayland::Commit(const LayoutDeviceIntRegion& aInvalidRegion) {
+#ifdef MOZ_LOGGING
+ {
+ gfx::IntRect lockSize = aInvalidRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::Commit [%p] damage size [%d, %d] -> [%d x %d]"
+ "screenSize [%d x %d]\n",
+ (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
+ mLockedScreenRect.width, mLockedScreenRect.height));
+ LOGWAYLAND((" mDrawToWaylandBufferDirectly = %d\n",
+ mDrawToWaylandBufferDirectly));
+ }
+#endif
+
+ MutexAutoLock lock(mSurfaceLock);
+
+ if (mDrawToWaylandBufferDirectly) {
+ MOZ_ASSERT(mWaylandBuffer->IsLocked());
+ mWaylandBufferDamage.OrWith(aInvalidRegion);
+ UnlockWaylandBuffer();
+ } else {
+ CacheImageSurface(aInvalidRegion);
+ }
+
+ mBufferCommitAllowed = true;
+ if (FlushPendingCommitsLocked()) {
+ mWaylandDisplay->QueueSyncBegin();
+ }
+}
+
+void WindowSurfaceWayland::FrameCallbackHandler() {
+ MOZ_ASSERT(mFrameCallback != nullptr,
+ "FrameCallbackHandler() called without valid frame callback!");
+ MOZ_ASSERT(mLastCommittedSurface != nullptr,
+ "FrameCallbackHandler() called without valid wl_surface!");
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::FrameCallbackHandler [%p]\n", (void*)this));
+
+ MutexAutoLock lock(mSurfaceLock);
+
+ wl_callback_destroy(mFrameCallback);
+ mFrameCallback = nullptr;
+
+ if (FlushPendingCommitsLocked()) {
+ mWaylandDisplay->QueueSyncBegin();
+ }
+}
+
+} // namespace widget
+} // namespace mozilla