1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "WindowSurfaceWayland.h"
8 
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <sys/mman.h>
12 
13 #include "nsPrintfCString.h"
14 #include "mozilla/gfx/2D.h"
15 #include "mozilla/gfx/Tools.h"
16 #include "gfx2DGlue.h"
17 #include "gfxPlatform.h"
18 #include "MozContainer.h"
19 #include "mozilla/ScopeExit.h"
20 #include "mozilla/StaticPrefs_widget.h"
21 #include "mozilla/WidgetUtils.h"
22 #include "nsTArray.h"
23 
24 #ifdef MOZ_LOGGING
25 #  include "mozilla/Logging.h"
26 #  include "Units.h"
27 extern mozilla::LazyLogModule gWidgetWaylandLog;
28 #  define LOGWAYLAND(args) \
29     MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, args)
30 #else
31 #  define LOGWAYLAND(args)
32 #endif /* MOZ_LOGGING */
33 
34 // Maximal compositing timeout it miliseconds
35 #define COMPOSITING_TIMEOUT 200
36 
37 namespace mozilla::widget {
38 
39 /*
40   Wayland multi-thread rendering scheme
41 
42   Every rendering thread (main thread, compositor thread) contains its own
43   nsWaylandDisplay object connected to Wayland compositor (Mutter, Weston, etc.)
44 
45   WindowSurfaceWayland implements WindowSurface class and draws nsWindow by
46   WindowSurface interface (Lock, Commit) to screen through nsWaylandDisplay.
47 
48   ----------------------
49   | Wayland compositor |
50   ----------------------
51              ^
52              |
53   ----------------------
54   |  nsWaylandDisplay  |
55   ----------------------
56         ^          ^
57         |          |
58         |          |
59         |       ---------------------------------        ------------------
60         |       | WindowSurfaceWayland          |<------>| nsWindow       |
61         |       |                               |        ------------------
62         |       |  -----------------------      |
63         |       |  | WaylandShmBuffer    |      |
64         |       |  |                     |      |
65         |       |  | ------------------- |      |
66         |       |  | |  WaylandShmPool | |      |
67         |       |  | ------------------- |      |
68         |       |  -----------------------      |
69         |       |                               |
70         |       |  -----------------------      |
71         |       |  | WaylandShmBuffer    |      |
72         |       |  |                     |      |
73         |       |  | ------------------- |      |
74         |       |  | |  WaylandShmPool | |      |
75         |       |  | ------------------- |      |
76         |       |  -----------------------      |
77         |       ---------------------------------
78         |
79         |
80   ---------------------------------        ------------------
81   | WindowSurfaceWayland          |<------>| nsWindow       |
82   |                               |        ------------------
83   |  -----------------------      |
84   |  | WaylandShmBuffer    |      |
85   |  |                     |      |
86   |  | ------------------- |      |
87   |  | |  WaylandShmPool | |      |
88   |  | ------------------- |      |
89   |  -----------------------      |
90   |                               |
91   |  -----------------------      |
92   |  | WaylandShmBuffer    |      |
93   |  |                     |      |
94   |  | ------------------- |      |
95   |  | |  WaylandShmPool | |      |
96   |  | ------------------- |      |
97   |  -----------------------      |
98   ---------------------------------
99 
100 
101 nsWaylandDisplay
102 
103 Is our connection to Wayland display server,
104 holds our display connection (wl_display) and event queue (wl_event_queue).
105 
106 nsWaylandDisplay is created for every thread which sends data to Wayland
107 compositor. Wayland events for main thread is served by default Gtk+ loop,
108 for other threads (compositor) we must create wl_event_queue and run event loop.
109 
110 
111 WindowSurfaceWayland
112 
113 Is a Wayland implementation of WindowSurface class for WindowSurfaceProvider,
114 we implement Lock() and Commit() interfaces from WindowSurface
115 for actual drawing.
116 
117 One WindowSurfaceWayland draws one nsWindow so those are tied 1:1.
118 At Wayland level it holds one wl_surface object.
119 
120 To perform visualiation of nsWindow, WindowSurfaceWayland contains one
121 wl_surface and two wl_buffer objects (owned by WaylandShmBuffer)
122 as we use double buffering. When nsWindow drawing is finished to wl_buffer,
123 the wl_buffer is attached to wl_surface and it's sent to Wayland compositor.
124 
125 When there's no wl_buffer available for drawing (all wl_buffers are locked in
126 compositor for instance) we store the drawing to WindowImageSurface object
127 and draw later when wl_buffer becomes available or discard the
128 WindowImageSurface cache when whole screen is invalidated.
129 
130 WaylandShmBuffer
131 
132 Is a class which provides a wl_buffer for drawing.
133 Wl_buffer is a main Wayland object with actual graphics data.
134 Wl_buffer basically represent one complete window screen.
135 When double buffering is involved every window (GdkWindow for instance)
136 utilises two wl_buffers which are cycled. One is filed with data by application
137 and one is rendered by compositor.
138 
139 WaylandShmBuffer is implemented by shared memory (shm).
140 It owns wl_buffer object, owns WaylandShmPool
141 (which provides the shared memory) and ties them together.
142 
143 WaylandShmPool
144 
145 WaylandShmPool acts as a manager of shared memory for WaylandShmBuffer.
146 Allocates it, holds reference to it and releases it.
147 
148 We allocate shared memory (shm) by mmap(..., MAP_SHARED,...) as an interface
149 between us and wayland compositor. We draw our graphics data to the shm and
150 handle to wayland compositor by WaylandShmBuffer/WindowSurfaceWayland
151 (wl_buffer/wl_surface).
152 */
153 
154 #define EVENT_LOOP_DELAY (1000 / 240)
155 
156 static const struct wl_callback_listener sFrameListenerWindowSurfaceWayland = {
157     WindowSurfaceWayland::FrameCallbackHandler};
158 
WindowSurfaceWayland(nsWindow * aWindow)159 WindowSurfaceWayland::WindowSurfaceWayland(nsWindow* aWindow)
160     : mWindow(aWindow),
161       mWaylandDisplay(WaylandDisplayGet()),
162       mWaylandFullscreenDamage(false),
163       mFrameCallback(nullptr),
164       mLastCommittedSurfaceID(-1),
165       mLastCommitTime(0),
166       mDrawToWaylandBufferDirectly(true),
167       mCanSwitchWaylandBuffer(true),
168       mWLBufferIsDirty(false),
169       mBufferCommitAllowed(false),
170       mBufferNeedsClear(false),
171       mSmoothRendering(StaticPrefs::widget_wayland_smooth_rendering()),
172       mSurfaceReadyTimerID(),
173       mSurfaceLock("WindowSurfaceWayland lock") {
174   LOGWAYLAND(("WindowSurfaceWayland::WindowSurfaceWayland() [%p]\n", this));
175   // Use slow compositing on KDE only.
176   const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
177   if (currentDesktop && strstr(currentDesktop, "KDE") != nullptr) {
178     mSmoothRendering = CACHE_NONE;
179   }
180 }
181 
~WindowSurfaceWayland()182 WindowSurfaceWayland::~WindowSurfaceWayland() {
183   LOGWAYLAND(("WindowSurfaceWayland::~WindowSurfaceWayland() [%p]\n", this));
184 
185   MutexAutoLock lock(mSurfaceLock);
186 
187   if (mSurfaceReadyTimerID) {
188     g_source_remove(mSurfaceReadyTimerID);
189     mSurfaceReadyTimerID = 0;
190   }
191 
192   if (mWLBufferIsDirty) {
193     NS_WARNING("Deleted WindowSurfaceWayland with a pending commit!");
194   }
195 
196   if (mFrameCallback) {
197     wl_callback_destroy(mFrameCallback);
198   }
199 }
200 
CreateWaylandBuffer(const LayoutDeviceIntSize & aSize)201 WaylandShmBuffer* WindowSurfaceWayland::CreateWaylandBuffer(
202     const LayoutDeviceIntSize& aSize) {
203   int availableBuffer;
204 
205   LOGWAYLAND(("WindowSurfaceWayland::CreateWaylandBuffer %d x %d\n",
206               aSize.width, aSize.height));
207 
208   for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
209        availableBuffer++) {
210     if (!mShmBackupBuffer[availableBuffer] ||
211         (!mShmBackupBuffer[availableBuffer]->IsAttached() &&
212          !mShmBackupBuffer[availableBuffer]->IsMatchingSize(aSize))) {
213       break;
214     }
215   }
216 
217   // There isn't any free slot for additional buffer.
218   if (availableBuffer == BACK_BUFFER_NUM) {
219     LOGWAYLAND(("    no free buffer slot!\n"));
220     return nullptr;
221   }
222 
223   RefPtr<WaylandShmBuffer> buffer =
224       WaylandShmBuffer::Create(GetWaylandDisplay(), aSize);
225   if (!buffer) {
226     LOGWAYLAND(("    failed to create back buffer!\n"));
227     return nullptr;
228   }
229 
230   buffer->SetBufferReleaseFunc(
231       &WindowSurfaceWayland::BufferReleaseCallbackHandler);
232   buffer->SetBufferReleaseData(this);
233 
234   mShmBackupBuffer[availableBuffer] = buffer;
235   LOGWAYLAND(
236       ("    created new buffer %p at %d!\n", buffer.get(), availableBuffer));
237   return buffer.get();
238 }
239 
WaylandBufferFindAvailable(const LayoutDeviceIntSize & aSize)240 WaylandShmBuffer* WindowSurfaceWayland::WaylandBufferFindAvailable(
241     const LayoutDeviceIntSize& aSize) {
242   LOGWAYLAND(("WindowSurfaceWayland::WaylandBufferFindAvailable %d x %d\n",
243               aSize.width, aSize.height));
244 
245   // Try to find a buffer which matches the size
246   for (int availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
247        availableBuffer++) {
248     RefPtr<WaylandShmBuffer> buffer = mShmBackupBuffer[availableBuffer];
249     if (buffer && !buffer->IsAttached() && buffer->IsMatchingSize(aSize)) {
250       LOGWAYLAND(("    found match %d [%p]\n", availableBuffer, buffer.get()));
251       return buffer.get();
252     }
253   }
254 
255   LOGWAYLAND(("    no buffer available!\n"));
256   return nullptr;
257 }
258 
SetNewWaylandBuffer()259 WaylandShmBuffer* WindowSurfaceWayland::SetNewWaylandBuffer() {
260   LOGWAYLAND(
261       ("WindowSurfaceWayland::NewWaylandBuffer [%p] Requested buffer [%d "
262        "x %d]\n",
263        (void*)this, mWLBufferSize.width, mWLBufferSize.height));
264 
265   mWaylandBuffer = WaylandBufferFindAvailable(mWLBufferSize);
266   if (mWaylandBuffer) {
267     return mWaylandBuffer;
268   }
269 
270   mWaylandBuffer = CreateWaylandBuffer(mWLBufferSize);
271   return mWaylandBuffer;
272 }
273 
274 // Recent
GetWaylandBuffer()275 WaylandShmBuffer* WindowSurfaceWayland::GetWaylandBuffer() {
276   LOGWAYLAND(
277       ("WindowSurfaceWayland::GetWaylandBuffer [%p] Requested buffer [%d "
278        "x %d] can switch %d\n",
279        (void*)this, mWLBufferSize.width, mWLBufferSize.height,
280        mCanSwitchWaylandBuffer));
281 
282 #if MOZ_LOGGING
283   LOGWAYLAND(("    Recent WaylandShmBuffer [%p]\n", mWaylandBuffer.get()));
284   for (int i = 0; i < BACK_BUFFER_NUM; i++) {
285     if (!mShmBackupBuffer[i]) {
286       LOGWAYLAND(("        WaylandShmBuffer [%d] null\n", i));
287     } else {
288       LOGWAYLAND(
289           ("        WaylandShmBuffer [%d][%p] width %d height %d attached %d\n",
290            i, mShmBackupBuffer[i].get(), mShmBackupBuffer[i]->GetSize().width,
291            mShmBackupBuffer[i]->GetSize().height,
292            mShmBackupBuffer[i]->IsAttached()));
293     }
294   }
295 #endif
296 
297   // There's no buffer created yet, create a new one for partial screen updates.
298   if (!mWaylandBuffer) {
299     return SetNewWaylandBuffer();
300   }
301 
302   if (mWaylandBuffer->IsAttached()) {
303     if (mCanSwitchWaylandBuffer) {
304       return SetNewWaylandBuffer();
305     }
306     LOGWAYLAND(("    Buffer is attached and we can't switch, return null\n"));
307     return nullptr;
308   }
309 
310   if (mWaylandBuffer->IsMatchingSize(mWLBufferSize)) {
311     LOGWAYLAND(("    Size is ok, use the buffer [%d x %d]\n",
312                 mWLBufferSize.width, mWLBufferSize.height));
313     return mWaylandBuffer;
314   }
315 
316   if (mCanSwitchWaylandBuffer) {
317     return SetNewWaylandBuffer();
318   }
319 
320   LOGWAYLAND(
321       ("    Buffer size does not match, requested %d x %d got %d x%d, return "
322        "null.\n",
323        mWaylandBuffer->GetSize().width, mWaylandBuffer->GetSize().height,
324        mWLBufferSize.width, mWLBufferSize.height));
325   return nullptr;
326 }
327 
LockWaylandBuffer()328 already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::LockWaylandBuffer() {
329   // Allocated wayland buffer must match mozcontainer widget size.
330   mWLBufferSize = mWindow->GetMozContainerSize();
331 
332   LOGWAYLAND(
333       ("WindowSurfaceWayland::LockWaylandBuffer [%p] Requesting buffer %d x "
334        "%d\n",
335        (void*)this, mWLBufferSize.width, mWLBufferSize.height));
336 
337   WaylandShmBuffer* buffer = GetWaylandBuffer();
338   LOGWAYLAND(("WindowSurfaceWayland::LockWaylandBuffer [%p] Got buffer %p\n",
339               (void*)this, (void*)buffer));
340 
341   if (!buffer) {
342     if (mLastCommitTime && (g_get_monotonic_time() / 1000) - mLastCommitTime >
343                                COMPOSITING_TIMEOUT) {
344       NS_WARNING(
345           "Slow response from Wayland compositor, visual glitches ahead.");
346     }
347     return nullptr;
348   }
349 
350   mCanSwitchWaylandBuffer = false;
351 
352   if (mBufferNeedsClear) {
353     buffer->Clear();
354     mBufferNeedsClear = false;
355   }
356 
357   return buffer->Lock();
358 }
359 
LockImageSurface(const gfx::IntSize & aLockSize)360 already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::LockImageSurface(
361     const gfx::IntSize& aLockSize) {
362   if (!mImageSurface || !(aLockSize <= mImageSurface->GetSize())) {
363     mImageSurface = gfx::Factory::CreateDataSourceSurface(
364         aLockSize, WaylandShmBuffer::GetSurfaceFormat());
365   }
366   gfx::DataSourceSurface::MappedSurface map = {nullptr, 0};
367   if (!mImageSurface->Map(gfx::DataSourceSurface::READ_WRITE, &map)) {
368     return nullptr;
369   }
370   return gfxPlatform::CreateDrawTargetForData(
371       map.mData, mImageSurface->GetSize(), map.mStride,
372       WaylandShmBuffer::GetSurfaceFormat());
373 }
374 
IsWindowFullScreenUpdate(LayoutDeviceIntSize & aScreenSize,const LayoutDeviceIntRegion & aUpdatedRegion)375 static bool IsWindowFullScreenUpdate(
376     LayoutDeviceIntSize& aScreenSize,
377     const LayoutDeviceIntRegion& aUpdatedRegion) {
378   if (aUpdatedRegion.GetNumRects() > 1) return false;
379 
380   gfx::IntRect rect = aUpdatedRegion.RectIter().Get().ToUnknownRect();
381   return (rect.x == 0 && rect.y == 0 && aScreenSize.width == rect.width &&
382           aScreenSize.height == rect.height);
383 }
384 
IsPopupFullScreenUpdate(LayoutDeviceIntSize & aScreenSize,const LayoutDeviceIntRegion & aUpdatedRegion)385 static bool IsPopupFullScreenUpdate(
386     LayoutDeviceIntSize& aScreenSize,
387     const LayoutDeviceIntRegion& aUpdatedRegion) {
388   // We know that popups can be drawn from two parts; a panel and an arrow.
389   // Assume we redraw whole popups when we have two rects and bounding
390   // box is equal to window borders.
391   if (aUpdatedRegion.GetNumRects() > 2) return false;
392 
393   gfx::IntRect lockSize = aUpdatedRegion.GetBounds().ToUnknownRect();
394   return (lockSize.x == 0 && lockSize.y == 0 &&
395           aScreenSize.width == lockSize.width &&
396           aScreenSize.height == lockSize.height);
397 }
398 
Lock(const LayoutDeviceIntRegion & aRegion)399 already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::Lock(
400     const LayoutDeviceIntRegion& aRegion) {
401   if (mWindow->WindowType() == eWindowType_invisible) {
402     return nullptr;
403   }
404 
405   // Lock the surface *after* WaitForSyncEnd() call as is can fire
406   // FlushPendingCommits().
407   MutexAutoLock lock(mSurfaceLock);
408 
409   // Disable all commits (from potential frame callback/delayed handlers)
410   // until next WindowSurfaceWayland::Commit() call.
411   mBufferCommitAllowed = false;
412 
413   LayoutDeviceIntSize mozContainerSize = mWindow->GetMozContainerSize();
414   gfx::IntRect lockSize = aRegion.GetBounds().ToUnknownRect();
415 
416   bool isTransparentPopup =
417       mWindow->IsWaylandPopup() &&
418       (eTransparencyTransparent == mWindow->GetTransparencyMode());
419 
420   bool windowRedraw = isTransparentPopup
421                           ? IsPopupFullScreenUpdate(mozContainerSize, aRegion)
422                           : IsWindowFullScreenUpdate(mozContainerSize, aRegion);
423   if (windowRedraw) {
424     // Clear buffer when we (re)draw new transparent popup window,
425     // otherwise leave it as-is, mBufferNeedsClear can be set from previous
426     // (already pending) commits which are cached now.
427     mBufferNeedsClear =
428         mWindow->WaylandSurfaceNeedsClear() || isTransparentPopup;
429 
430     // We do full buffer repaint so clear our cached drawings.
431     mDelayedImageCommits.Clear();
432     mWaylandBufferDamage.SetEmpty();
433     mCanSwitchWaylandBuffer = true;
434     mWLBufferIsDirty = false;
435 
436     // Store info that we can safely invalidate whole screen.
437     mWaylandFullscreenDamage = true;
438   } else {
439     // We can switch buffer if there isn't any content committed
440     // to active buffer.
441     mCanSwitchWaylandBuffer = !mWLBufferIsDirty;
442   }
443 
444   LOGWAYLAND(
445       ("WindowSurfaceWayland::Lock [%p] [%d,%d] -> [%d x %d] rects %d "
446        "MozContainer size [%d x %d]\n",
447        (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
448        aRegion.GetNumRects(), mozContainerSize.width, mozContainerSize.height));
449   LOGWAYLAND(("   nsWindow = %p\n", mWindow));
450   LOGWAYLAND(("   isPopup = %d\n", mWindow->IsWaylandPopup()));
451   LOGWAYLAND(("   isTransparentPopup = %d\n", isTransparentPopup));
452   LOGWAYLAND(("   IsPopupFullScreenUpdate = %d\n",
453               IsPopupFullScreenUpdate(mozContainerSize, aRegion)));
454   LOGWAYLAND(("   IsWindowFullScreenUpdate = %d\n",
455               IsWindowFullScreenUpdate(mozContainerSize, aRegion)));
456   LOGWAYLAND(("   mBufferNeedsClear = %d\n", mBufferNeedsClear));
457   LOGWAYLAND(("   mWLBufferIsDirty = %d\n", mWLBufferIsDirty));
458   LOGWAYLAND(("   mCanSwitchWaylandBuffer = %d\n", mCanSwitchWaylandBuffer));
459   LOGWAYLAND(("   windowRedraw = %d\n", windowRedraw));
460 
461   if (!(mMozContainerSize == mozContainerSize)) {
462     LOGWAYLAND(("   screen size changed\n"));
463     if (!windowRedraw) {
464       LOGWAYLAND(("   screen size changed without redraw!\n"));
465       // Screen (window) size changed and we still have some painting pending
466       // for the last window size. That can happen when window is resized.
467       // We won't draw it but wait for new content.
468       mDelayedImageCommits.Clear();
469       mWaylandBufferDamage.SetEmpty();
470       mCanSwitchWaylandBuffer = true;
471       mWLBufferIsDirty = false;
472       mBufferNeedsClear = true;
473     }
474     mMozContainerSize = mozContainerSize;
475   }
476 
477   mDrawToWaylandBufferDirectly = windowRedraw || mSmoothRendering == CACHE_NONE;
478   if (!mDrawToWaylandBufferDirectly && mSmoothRendering == CACHE_SMALL) {
479     mDrawToWaylandBufferDirectly =
480         (lockSize.width * 2 > mozContainerSize.width &&
481          lockSize.height * 2 > mozContainerSize.height);
482   }
483 
484   if (!mDrawToWaylandBufferDirectly) {
485     // Don't switch wl_buffers when we cache drawings.
486     mCanSwitchWaylandBuffer = false;
487     LOGWAYLAND(("   Indirect drawing, mCanSwitchWaylandBuffer = %d\n",
488                 mCanSwitchWaylandBuffer));
489   }
490 
491   if (mDrawToWaylandBufferDirectly) {
492     LOGWAYLAND(("   Direct drawing\n"));
493     RefPtr<gfx::DrawTarget> dt = LockWaylandBuffer();
494     if (dt) {
495 #if MOZ_LOGGING
496       mWaylandBuffer->DumpToFile("Lock");
497 #endif
498       if (!windowRedraw) {
499         DrawDelayedImageCommits(dt, mWaylandBufferDamage);
500 #if MOZ_LOGGING
501         mWaylandBuffer->DumpToFile("Lock-after-commit");
502 #endif
503       }
504       mWLBufferIsDirty = true;
505       return dt.forget();
506     }
507   }
508 
509   // We do indirect drawing because there isn't any front buffer available.
510   // Do indirect drawing to mImageSurface which is commited to wayland
511   // wl_buffer by DrawDelayedImageCommits() later.
512   mDrawToWaylandBufferDirectly = false;
513 
514   LOGWAYLAND(("   Indirect drawing.\n"));
515   return LockImageSurface(gfx::IntSize(lockSize.XMost(), lockSize.YMost()));
516 }
517 
OverlapsSurface(class WindowImageSurface & aBottomSurface)518 bool WindowImageSurface::OverlapsSurface(
519     class WindowImageSurface& aBottomSurface) {
520   return mUpdateRegion.Contains(aBottomSurface.mUpdateRegion);
521 }
522 
DrawToTarget(gfx::DrawTarget * aDest,LayoutDeviceIntRegion & aWaylandBufferDamage)523 void WindowImageSurface::DrawToTarget(
524     gfx::DrawTarget* aDest, LayoutDeviceIntRegion& aWaylandBufferDamage) {
525 #ifdef MOZ_LOGGING
526   gfx::IntRect bounds = mUpdateRegion.GetBounds().ToUnknownRect();
527   LOGWAYLAND(("WindowImageSurface::DrawToTarget\n"));
528   LOGWAYLAND(("    rects num %d\n", mUpdateRegion.GetNumRects()));
529   LOGWAYLAND(("    bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
530               bounds.width, bounds.height));
531 #endif
532   for (auto iter = mUpdateRegion.RectIter(); !iter.Done(); iter.Next()) {
533     gfx::IntRect r(iter.Get().ToUnknownRect());
534     LOGWAYLAND(
535         ("    draw rect [%d,%d] -> [%d x %d]\n", r.x, r.y, r.width, r.height));
536     aDest->CopySurface(mImageSurface, r, gfx::IntPoint(r.x, r.y));
537   }
538   aWaylandBufferDamage.OrWith(mUpdateRegion);
539 }
540 
WindowImageSurface(gfx::DataSourceSurface * aImageSurface,const LayoutDeviceIntRegion & aUpdateRegion)541 WindowImageSurface::WindowImageSurface(
542     gfx::DataSourceSurface* aImageSurface,
543     const LayoutDeviceIntRegion& aUpdateRegion)
544     : mImageSurface(aImageSurface), mUpdateRegion(aUpdateRegion) {}
545 
DrawDelayedImageCommits(gfx::DrawTarget * aDrawTarget,LayoutDeviceIntRegion & aWaylandBufferDamage)546 bool WindowSurfaceWayland::DrawDelayedImageCommits(
547     gfx::DrawTarget* aDrawTarget, LayoutDeviceIntRegion& aWaylandBufferDamage) {
548   unsigned int imagesNum = mDelayedImageCommits.Length();
549   LOGWAYLAND(("WindowSurfaceWayland::DrawDelayedImageCommits [%p] len %d\n",
550               (void*)this, imagesNum));
551   for (unsigned int i = 0; i < imagesNum; i++) {
552     mDelayedImageCommits[i].DrawToTarget(aDrawTarget, aWaylandBufferDamage);
553   }
554   mDelayedImageCommits.Clear();
555 
556   return (imagesNum != 0);
557 }
558 
CacheImageSurface(const LayoutDeviceIntRegion & aRegion)559 void WindowSurfaceWayland::CacheImageSurface(
560     const LayoutDeviceIntRegion& aRegion) {
561 #ifdef MOZ_LOGGING
562   gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
563   LOGWAYLAND(("WindowSurfaceWayland::CacheImageSurface [%p]\n", (void*)this));
564   LOGWAYLAND(("    rects num %d\n", aRegion.GetNumRects()));
565   LOGWAYLAND(("    bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
566               bounds.width, bounds.height));
567 #endif
568 
569   mImageSurface->Unmap();
570   WindowImageSurface surf = WindowImageSurface(mImageSurface, aRegion);
571 
572   if (mDelayedImageCommits.Length()) {
573     auto lastSurf = mDelayedImageCommits.PopLastElement();
574     if (surf.OverlapsSurface(lastSurf)) {
575 #ifdef MOZ_LOGGING
576       {
577         gfx::IntRect size =
578             lastSurf.GetUpdateRegion()->GetBounds().ToUnknownRect();
579         LOGWAYLAND(("    removing [ %d, %d] -> [%d x %d]\n", size.x, size.y,
580                     size.width, size.height));
581       }
582 #endif
583     } else {
584       mDelayedImageCommits.AppendElement(lastSurf);
585     }
586   }
587 
588   mDelayedImageCommits.AppendElement(surf);
589   // mImageSurface is owned by mDelayedImageCommits
590   mImageSurface = nullptr;
591 
592   LOGWAYLAND(
593       ("    There's %d cached images\n", int(mDelayedImageCommits.Length())));
594 }
595 
CommitImageCacheToWaylandBuffer()596 bool WindowSurfaceWayland::CommitImageCacheToWaylandBuffer() {
597   if (!mDelayedImageCommits.Length()) {
598     return false;
599   }
600 
601   MOZ_ASSERT(!mDrawToWaylandBufferDirectly);
602 
603   RefPtr<gfx::DrawTarget> dt = LockWaylandBuffer();
604   if (!dt) {
605     return false;
606   }
607 
608   LOGWAYLAND(("   Flushing %ld cached WindowImageSurfaces to Wayland buffer\n",
609               long(mDelayedImageCommits.Length())));
610 
611   return DrawDelayedImageCommits(dt, mWaylandBufferDamage);
612 }
613 
FlushPendingCommits()614 void WindowSurfaceWayland::FlushPendingCommits() {
615   MutexAutoLock lock(mSurfaceLock);
616   if (FlushPendingCommitsLocked()) {
617     mWaylandDisplay->QueueSyncBegin();
618   }
619 }
620 
621 // When a new window is created we may not have a valid wl_surface
622 // for drawing (Gtk haven't created it yet). All commits are queued
623 // and FlushPendingCommitsLocked() is called by timer when wl_surface is ready
624 // for drawing.
WaylandBufferFlushPendingCommits(void * data)625 static int WaylandBufferFlushPendingCommits(void* data) {
626   WindowSurfaceWayland* aSurface = static_cast<WindowSurfaceWayland*>(data);
627   aSurface->FlushPendingCommits();
628   return true;
629 }
630 
FlushPendingCommitsLocked()631 bool WindowSurfaceWayland::FlushPendingCommitsLocked() {
632   LOGWAYLAND(
633       ("WindowSurfaceWayland::FlushPendingCommitsLocked [%p]\n", (void*)this));
634   LOGWAYLAND(("    mDrawToWaylandBufferDirectly = %d\n",
635               mDrawToWaylandBufferDirectly));
636   LOGWAYLAND(("    mCanSwitchWaylandBuffer = %d\n", mCanSwitchWaylandBuffer));
637   LOGWAYLAND(("    mFrameCallback = %p\n", mFrameCallback));
638   LOGWAYLAND(("    mLastCommittedSurfaceID = %d\n", mLastCommittedSurfaceID));
639   LOGWAYLAND(("    mWLBufferIsDirty = %d\n", mWLBufferIsDirty));
640   LOGWAYLAND(("    mBufferCommitAllowed = %d\n", mBufferCommitAllowed));
641 
642   if (!mBufferCommitAllowed) {
643     LOGWAYLAND(("    Quit - buffer commit is not allowed.\n"));
644     return false;
645   }
646 
647   if (CommitImageCacheToWaylandBuffer()) {
648     mWLBufferIsDirty = true;
649   }
650 
651   // There's nothing to do here
652   if (!mWLBufferIsDirty) {
653     LOGWAYLAND(("    Quit - no pending commit.\n"));
654     return false;
655   }
656 
657   MOZ_ASSERT(!mWaylandBuffer->IsAttached(),
658              "We can't draw to attached wayland buffer!");
659 
660   LOGWAYLAND(("    Drawing pending commits.\n"));
661   MozContainer* container = mWindow->GetMozContainer();
662   wl_surface* waylandSurface = moz_container_wayland_surface_lock(container);
663   if (!waylandSurface) {
664     LOGWAYLAND(
665         ("    moz_container_wayland_surface_lock() failed, delay commit.\n"));
666 
667     if (!mSurfaceReadyTimerID) {
668       mSurfaceReadyTimerID = (int)g_timeout_add(
669           EVENT_LOOP_DELAY, &WaylandBufferFlushPendingCommits, this);
670     }
671     return true;
672   }
673   if (mSurfaceReadyTimerID) {
674     g_source_remove(mSurfaceReadyTimerID);
675     mSurfaceReadyTimerID = 0;
676   }
677 
678   LOGWAYLAND(("    We have wl_surface %p ID [%d] to commit in.\n",
679               waylandSurface,
680               wl_proxy_get_id((struct wl_proxy*)waylandSurface)));
681 
682   auto unlockContainer = MakeScopeExit([&] {
683     moz_container_wayland_surface_unlock(container, &waylandSurface);
684   });
685 
686   wl_proxy_set_queue((struct wl_proxy*)waylandSurface,
687                      mWaylandDisplay->GetEventQueue());
688 
689   // We can't use frame callbacks from previous surfaces
690   if (moz_container_wayland_get_and_reset_remapped(container)) {
691     mLastCommittedSurfaceID = -1;
692     g_clear_pointer(&mFrameCallback, wl_callback_destroy);
693   }
694 
695   // We have an active frame callback request so handle it.
696   if (mFrameCallback) {
697     int waylandSurfaceID =
698         (int)wl_proxy_get_id((struct wl_proxy*)waylandSurface);
699     if (waylandSurfaceID == mLastCommittedSurfaceID) {
700       LOGWAYLAND(("    [%p] wait for frame callback ID %d.\n", (void*)this,
701                   waylandSurfaceID));
702       // We have an active frame callback pending from our recent surface.
703       // It means we should defer the commit to FrameCallbackHandler().
704       return true;
705     }
706     LOGWAYLAND(("    Removing wrong frame callback [%p] ID %d.\n",
707                 mFrameCallback,
708                 wl_proxy_get_id((struct wl_proxy*)mFrameCallback)));
709     // If our stored wl_surface does not match the actual one it means the frame
710     // callback is no longer active and we should release it.
711     wl_callback_destroy(mFrameCallback);
712     mFrameCallback = nullptr;
713     mLastCommittedSurfaceID = -1;
714   }
715 
716   if (mWaylandFullscreenDamage) {
717     LOGWAYLAND(("    wl_surface_damage full screen\n"));
718     wl_surface_damage_buffer(waylandSurface, 0, 0, INT_MAX, INT_MAX);
719   } else {
720     for (auto iter = mWaylandBufferDamage.RectIter(); !iter.Done();
721          iter.Next()) {
722       mozilla::LayoutDeviceIntRect r = iter.Get();
723       LOGWAYLAND(("   wl_surface_damage_buffer [%d, %d] -> [%d, %d]\n", r.x,
724                   r.y, r.width, r.height));
725       wl_surface_damage_buffer(waylandSurface, r.x, r.y, r.width, r.height);
726     }
727   }
728 
729 #if MOZ_LOGGING
730   mWaylandBuffer->DumpToFile("Commit");
731 #endif
732 
733   // Clear all back buffer damage as we're committing
734   // all requested regions.
735   mWaylandFullscreenDamage = false;
736   mWaylandBufferDamage.SetEmpty();
737 
738   mFrameCallback = wl_surface_frame(waylandSurface);
739   wl_callback_add_listener(mFrameCallback, &sFrameListenerWindowSurfaceWayland,
740                            this);
741 
742   mWaylandBuffer->AttachAndCommit(waylandSurface);
743   wl_display_flush(GetWaylandDisplay()->GetDisplay());
744 
745   mLastCommittedSurfaceID =
746       (int)wl_proxy_get_id((struct wl_proxy*)waylandSurface);
747   mLastCommitTime = g_get_monotonic_time() / 1000;
748 
749   // There's no pending commit, all changes are sent to compositor.
750   mWLBufferIsDirty = false;
751 
752   return true;
753 }
754 
Commit(const LayoutDeviceIntRegion & aInvalidRegion)755 void WindowSurfaceWayland::Commit(const LayoutDeviceIntRegion& aInvalidRegion) {
756 #ifdef MOZ_LOGGING
757   {
758     gfx::IntRect lockSize = aInvalidRegion.GetBounds().ToUnknownRect();
759     LOGWAYLAND(
760         ("WindowSurfaceWayland::Commit [%p] damage size [%d, %d] -> [%d x %d] "
761          "MozContainer [%d x %d]\n",
762          (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
763          mMozContainerSize.width, mMozContainerSize.height));
764     LOGWAYLAND(("    mDrawToWaylandBufferDirectly = %d\n",
765                 mDrawToWaylandBufferDirectly));
766   }
767 #endif
768 
769   MutexAutoLock lock(mSurfaceLock);
770 
771   if (mDrawToWaylandBufferDirectly) {
772     mWaylandBufferDamage.OrWith(aInvalidRegion);
773   } else {
774     CacheImageSurface(aInvalidRegion);
775   }
776 
777   mBufferCommitAllowed = true;
778   if (FlushPendingCommitsLocked()) {
779     mWaylandDisplay->QueueSyncBegin();
780   }
781 }
782 
FrameCallbackHandler()783 void WindowSurfaceWayland::FrameCallbackHandler() {
784   MOZ_ASSERT(mFrameCallback != nullptr,
785              "FrameCallbackHandler() called without valid frame callback!");
786   MOZ_ASSERT(mLastCommittedSurfaceID != -1,
787              "FrameCallbackHandler() called without valid wl_surface!");
788   LOGWAYLAND(("WindowSurfaceWayland::FrameCallbackHandler [%p]\n", this));
789 
790   MutexAutoLock lock(mSurfaceLock);
791 
792   wl_callback_destroy(mFrameCallback);
793   mFrameCallback = nullptr;
794 
795   if (FlushPendingCommitsLocked()) {
796     mWaylandDisplay->QueueSyncBegin();
797   }
798 }
799 
FrameCallbackHandler(void * aData,struct wl_callback * aCallback,uint32_t aTime)800 void WindowSurfaceWayland::FrameCallbackHandler(void* aData,
801                                                 struct wl_callback* aCallback,
802                                                 uint32_t aTime) {
803   auto* surface = reinterpret_cast<WindowSurfaceWayland*>(aData);
804   surface->FrameCallbackHandler();
805 }
806 
BufferReleaseCallbackHandler(wl_buffer * aBuffer)807 void WindowSurfaceWayland::BufferReleaseCallbackHandler(wl_buffer* aBuffer) {
808   FlushPendingCommits();
809 }
810 
BufferReleaseCallbackHandler(void * aData,wl_buffer * aBuffer)811 void WindowSurfaceWayland::BufferReleaseCallbackHandler(void* aData,
812                                                         wl_buffer* aBuffer) {
813   auto* surface = reinterpret_cast<WindowSurfaceWayland*>(aData);
814   surface->BufferReleaseCallbackHandler(aBuffer);
815 }
816 
817 }  // namespace mozilla::widget
818