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