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