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 "WindowSurfaceWaylandMultiBuffer.h"
8 
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <sys/mman.h>
12 
13 #include "gfx2DGlue.h"
14 #include "gfxPlatform.h"
15 #include "MozContainer.h"
16 #include "mozilla/gfx/DataSurfaceHelpers.h"
17 #include "mozilla/gfx/Tools.h"
18 #include "mozilla/ScopeExit.h"
19 #include "mozilla/StaticPrefs_widget.h"
20 #include "mozilla/WidgetUtils.h"
21 
22 #undef LOG
23 #ifdef MOZ_LOGGING
24 #  include "mozilla/Logging.h"
25 #  include "Units.h"
26 extern mozilla::LazyLogModule gWidgetWaylandLog;
27 #  define LOGWAYLAND(args) \
28     MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, args)
29 #else
30 #  define LOGWAYLAND(args)
31 #endif /* MOZ_LOGGING */
32 
33 namespace mozilla::widget {
34 
35 /*
36   Wayland multi-thread rendering scheme
37 
38   Every rendering thread (main thread, compositor thread) contains its own
39   nsWaylandDisplay object connected to Wayland compositor (Mutter, Weston, etc.)
40 
41   WindowSurfaceWayland implements WindowSurface class and draws nsWindow by
42   WindowSurface interface (Lock, Commit) to screen through nsWaylandDisplay.
43 
44   ----------------------
45   | Wayland compositor |
46   ----------------------
47              ^
48              |
49   ----------------------
50   |  nsWaylandDisplay  |
51   ----------------------
52         ^          ^
53         |          |
54         |          |
55         |       ---------------------------------        ------------------
56         |       | WindowSurfaceWayland          |<------>| nsWindow       |
57         |       |                               |        ------------------
58         |       |  -----------------------      |
59         |       |  | WaylandShmBuffer    |      |
60         |       |  |                     |      |
61         |       |  | ------------------- |      |
62         |       |  | |  WaylandShmPool | |      |
63         |       |  | ------------------- |      |
64         |       |  -----------------------      |
65         |       |                               |
66         |       |  -----------------------      |
67         |       |  | WaylandShmBuffer    |      |
68         |       |  |                     |      |
69         |       |  | ------------------- |      |
70         |       |  | |  WaylandShmPool | |      |
71         |       |  | ------------------- |      |
72         |       |  -----------------------      |
73         |       ---------------------------------
74         |
75         |
76   ---------------------------------        ------------------
77   | WindowSurfaceWayland          |<------>| nsWindow       |
78   |                               |        ------------------
79   |  -----------------------      |
80   |  | WaylandShmBuffer    |      |
81   |  |                     |      |
82   |  | ------------------- |      |
83   |  | |  WaylandShmPool | |      |
84   |  | ------------------- |      |
85   |  -----------------------      |
86   |                               |
87   |  -----------------------      |
88   |  | WaylandShmBuffer    |      |
89   |  |                     |      |
90   |  | ------------------- |      |
91   |  | |  WaylandShmPool | |      |
92   |  | ------------------- |      |
93   |  -----------------------      |
94   ---------------------------------
95 
96 
97 nsWaylandDisplay
98 
99 Is our connection to Wayland display server,
100 holds our display connection (wl_display) and event queue (wl_event_queue).
101 
102 nsWaylandDisplay is created for every thread which sends data to Wayland
103 compositor. Wayland events for main thread is served by default Gtk+ loop,
104 for other threads (compositor) we must create wl_event_queue and run event loop.
105 
106 
107 WindowSurfaceWayland
108 
109 Is a Wayland implementation of WindowSurface class for WindowSurfaceProvider,
110 we implement Lock() and Commit() interfaces from WindowSurface
111 for actual drawing.
112 
113 One WindowSurfaceWayland draws one nsWindow so those are tied 1:1.
114 At Wayland level it holds one wl_surface object.
115 
116 To perform visualiation of nsWindow, WindowSurfaceWayland contains one
117 wl_surface and two wl_buffer objects (owned by WaylandShmBuffer)
118 as we use double buffering. When nsWindow drawing is finished to wl_buffer,
119 the wl_buffer is attached to wl_surface and it's sent to Wayland compositor.
120 
121 When there's no wl_buffer available for drawing (all wl_buffers are locked in
122 compositor for instance) we store the drawing to WindowImageSurface object
123 and draw later when wl_buffer becomes available or discard the
124 WindowImageSurface cache when whole screen is invalidated.
125 
126 WaylandShmBuffer
127 
128 Is a class which provides a wl_buffer for drawing.
129 Wl_buffer is a main Wayland object with actual graphics data.
130 Wl_buffer basically represent one complete window screen.
131 When double buffering is involved every window (GdkWindow for instance)
132 utilises two wl_buffers which are cycled. One is filed with data by application
133 and one is rendered by compositor.
134 
135 WaylandShmBuffer is implemented by shared memory (shm).
136 It owns wl_buffer object, owns WaylandShmPool
137 (which provides the shared memory) and ties them together.
138 
139 WaylandShmPool
140 
141 WaylandShmPool acts as a manager of shared memory for WaylandShmBuffer.
142 Allocates it, holds reference to it and releases it.
143 
144 We allocate shared memory (shm) by mmap(..., MAP_SHARED,...) as an interface
145 between us and wayland compositor. We draw our graphics data to the shm and
146 handle to wayland compositor by WaylandShmBuffer/WindowSurfaceWayland
147 (wl_buffer/wl_surface).
148 */
149 
150 using gfx::DataSourceSurface;
151 
152 #define BACK_BUFFER_NUM 3
153 
WindowSurfaceWaylandMB(nsWindow * aWindow)154 WindowSurfaceWaylandMB::WindowSurfaceWaylandMB(nsWindow* aWindow)
155     : mSurfaceLock("WindowSurfaceWayland lock"),
156       mWindow(aWindow),
157       mWaylandDisplay(WaylandDisplayGet()),
158       mWaylandBuffer(nullptr) {}
159 
ObtainBufferFromPool(const LayoutDeviceIntSize & aSize)160 RefPtr<WaylandShmBuffer> WindowSurfaceWaylandMB::ObtainBufferFromPool(
161     const LayoutDeviceIntSize& aSize) {
162   if (!mAvailableBuffers.IsEmpty()) {
163     RefPtr<WaylandShmBuffer> buffer = mAvailableBuffers.PopLastElement();
164     mInUseBuffers.AppendElement(buffer);
165     return buffer;
166   }
167 
168   RefPtr<WaylandShmBuffer> buffer =
169       WaylandShmBuffer::Create(GetWaylandDisplay(), aSize);
170   mInUseBuffers.AppendElement(buffer);
171 
172   buffer->SetBufferReleaseFunc(
173       &WindowSurfaceWaylandMB::BufferReleaseCallbackHandler);
174   buffer->SetBufferReleaseData(this);
175 
176   return buffer;
177 }
178 
ReturnBufferToPool(const RefPtr<WaylandShmBuffer> & aBuffer)179 void WindowSurfaceWaylandMB::ReturnBufferToPool(
180     const RefPtr<WaylandShmBuffer>& aBuffer) {
181   for (const RefPtr<WaylandShmBuffer>& buffer : mInUseBuffers) {
182     if (buffer == aBuffer) {
183       if (buffer->IsMatchingSize(mMozContainerSize)) {
184         mAvailableBuffers.AppendElement(buffer);
185       }
186       mInUseBuffers.RemoveElement(buffer);
187       return;
188     }
189   }
190   MOZ_RELEASE_ASSERT(false, "Returned buffer not in use");
191 }
192 
EnforcePoolSizeLimit(const MutexAutoLock & aLock)193 void WindowSurfaceWaylandMB::EnforcePoolSizeLimit(const MutexAutoLock& aLock) {
194   // Enforce the pool size limit, removing least-recently-used entries as
195   // necessary.
196   while (mAvailableBuffers.Length() > BACK_BUFFER_NUM) {
197     mAvailableBuffers.RemoveElementAt(0);
198   }
199 
200   NS_WARNING_ASSERTION(mInUseBuffers.Length() < 10, "We are leaking buffers");
201 }
202 
PrepareBufferForFrame(const MutexAutoLock & aLock)203 void WindowSurfaceWaylandMB::PrepareBufferForFrame(const MutexAutoLock& aLock) {
204   if (mWindow->WindowType() == eWindowType_invisible) {
205     return;
206   }
207 
208   LayoutDeviceIntSize newMozContainerSize = mWindow->GetMozContainerSize();
209   if (mMozContainerSize != newMozContainerSize) {
210     mMozContainerSize = newMozContainerSize;
211     mPreviousWaylandBuffer = nullptr;
212     mPreviousInvalidRegion.SetEmpty();
213     mAvailableBuffers.Clear();
214   }
215 
216   LOGWAYLAND(
217       ("WindowSurfaceWaylandMB::PrepareBufferForFrame [%p] MozContainer "
218        "size [%d x %d]\n",
219        (void*)this, mMozContainerSize.width, mMozContainerSize.height));
220 
221   MOZ_ASSERT(!mWaylandBuffer);
222   mWaylandBuffer = ObtainBufferFromPool(mMozContainerSize);
223 }
224 
Lock(const LayoutDeviceIntRegion & aRegion)225 already_AddRefed<DrawTarget> WindowSurfaceWaylandMB::Lock(
226     const LayoutDeviceIntRegion& aRegion) {
227   MutexAutoLock lock(mSurfaceLock);
228 
229 #ifdef MOZ_LOGGING
230   gfx::IntRect lockRect = aRegion.GetBounds().ToUnknownRect();
231   LOGWAYLAND(
232       ("WindowSurfaceWaylandMB::Lock [%p] [%d,%d] -> [%d x %d] rects %d "
233        "MozContainer size [%d x %d]\n",
234        (void*)this, lockRect.x, lockRect.y, lockRect.width, lockRect.height,
235        aRegion.GetNumRects(), mMozContainerSize.width,
236        mMozContainerSize.height));
237 #endif
238 
239   PrepareBufferForFrame(lock);
240   if (!mWaylandBuffer) {
241     return nullptr;
242   }
243 
244   RefPtr<DrawTarget> dt = mWaylandBuffer->Lock();
245   return dt.forget();
246 }
247 
HandlePartialUpdate(const MutexAutoLock & aLock,const LayoutDeviceIntRegion & aInvalidRegion)248 void WindowSurfaceWaylandMB::HandlePartialUpdate(
249     const MutexAutoLock& aLock, const LayoutDeviceIntRegion& aInvalidRegion) {
250   if (!mPreviousWaylandBuffer || mPreviousWaylandBuffer == mWaylandBuffer) {
251     mPreviousWaylandBuffer = mWaylandBuffer;
252     mPreviousInvalidRegion = aInvalidRegion;
253     return;
254   }
255 
256   LayoutDeviceIntRegion copyRegion;
257   if (mWaylandBuffer->GetBufferAge() == 2) {
258     copyRegion.Sub(mPreviousInvalidRegion, aInvalidRegion);
259   } else {
260     LayoutDeviceIntSize size = mPreviousWaylandBuffer->GetSize();
261     copyRegion = LayoutDeviceIntRegion(
262         LayoutDeviceIntRect(0, 0, size.width, size.height));
263     copyRegion.SubOut(aInvalidRegion);
264   }
265 
266   if (!copyRegion.IsEmpty()) {
267     RefPtr<DataSourceSurface> dataSourceSurface =
268         mozilla::gfx::CreateDataSourceSurfaceFromData(
269             mPreviousWaylandBuffer->GetSize().ToUnknownSize(),
270             mPreviousWaylandBuffer->GetSurfaceFormat(),
271             (const uint8_t*)mPreviousWaylandBuffer->GetShmPool()
272                 ->GetImageData(),
273             mPreviousWaylandBuffer->GetSize().width *
274                 BytesPerPixel(mPreviousWaylandBuffer->GetSurfaceFormat()));
275     RefPtr<DrawTarget> dt = mWaylandBuffer->Lock();
276 
277     for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
278       LayoutDeviceIntRect r = iter.Get();
279       dt->CopySurface(dataSourceSurface, r.ToUnknownRect(), IntPoint(r.x, r.y));
280     }
281   }
282 
283   mPreviousWaylandBuffer = mWaylandBuffer;
284   mPreviousInvalidRegion = aInvalidRegion;
285 }
286 
Commit(const LayoutDeviceIntRegion & aInvalidRegion)287 void WindowSurfaceWaylandMB::Commit(
288     const LayoutDeviceIntRegion& aInvalidRegion) {
289   MutexAutoLock lock(mSurfaceLock);
290 
291 #ifdef MOZ_LOGGING
292   gfx::IntRect invalidRect = aInvalidRegion.GetBounds().ToUnknownRect();
293   LOGWAYLAND(
294       ("WindowSurfaceWaylandMB::Commit [%p] damage rect [%d, %d] -> [%d x %d] "
295        "MozContainer [%d x %d]\n",
296        (void*)this, invalidRect.x, invalidRect.y, invalidRect.width,
297        invalidRect.height, mMozContainerSize.width, mMozContainerSize.height));
298 #endif
299 
300   if (!mWaylandBuffer) {
301     LOGWAYLAND(
302         ("WindowSurfaceWaylandMB::Commit [%p] frame skipped: no buffer\n",
303          (void*)this));
304     IncrementBufferAge();
305     return;
306   }
307 
308   MozContainer* container = mWindow->GetMozContainer();
309   wl_surface* waylandSurface = moz_container_wayland_surface_lock(container);
310   if (!waylandSurface) {
311     LOGWAYLAND(
312         ("WindowSurfaceWaylandMB::Commit [%p] frame skipped: can't lock "
313          "wl_surface\n",
314          (void*)this));
315     ReturnBufferToPool(mWaylandBuffer);
316     mWaylandBuffer = nullptr;
317     IncrementBufferAge();
318     return;
319   }
320 
321   HandlePartialUpdate(lock, aInvalidRegion);
322 
323   for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
324     LayoutDeviceIntRect r = iter.Get();
325     wl_surface_damage_buffer(waylandSurface, r.x, r.y, r.width, r.height);
326   }
327 
328   mWaylandBuffer->AttachAndCommit(waylandSurface);
329 
330   moz_container_wayland_surface_unlock(container, &waylandSurface);
331 
332   mWaylandBuffer->ResetBufferAge();
333   mWaylandBuffer = nullptr;
334 
335   EnforcePoolSizeLimit(lock);
336 
337   IncrementBufferAge();
338 
339   if (wl_display_flush(GetWaylandDisplay()->GetDisplay()) == -1) {
340     LOGWAYLAND(
341         ("WindowSurfaceWaylandMB::Commit [%p] flush failed\n", (void*)this));
342   }
343 }
344 
IncrementBufferAge()345 void WindowSurfaceWaylandMB::IncrementBufferAge() {
346   for (const RefPtr<WaylandShmBuffer>& buffer : mInUseBuffers) {
347     buffer->IncrementBufferAge();
348   }
349   for (const RefPtr<WaylandShmBuffer>& buffer : mAvailableBuffers) {
350     buffer->IncrementBufferAge();
351   }
352 }
353 
BufferReleaseCallbackHandler(wl_buffer * aBuffer)354 void WindowSurfaceWaylandMB::BufferReleaseCallbackHandler(wl_buffer* aBuffer) {
355   MutexAutoLock lock(mSurfaceLock);
356 
357   for (const RefPtr<WaylandShmBuffer>& buffer : mInUseBuffers) {
358     if (buffer->GetWlBuffer() == aBuffer) {
359       ReturnBufferToPool(buffer);
360       break;
361     }
362   }
363 }
364 
BufferReleaseCallbackHandler(void * aData,wl_buffer * aBuffer)365 void WindowSurfaceWaylandMB::BufferReleaseCallbackHandler(void* aData,
366                                                           wl_buffer* aBuffer) {
367   auto* surface = reinterpret_cast<WindowSurfaceWaylandMB*>(aData);
368   surface->BufferReleaseCallbackHandler(aBuffer);
369 }
370 
371 }  // namespace mozilla::widget
372