1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */
7 #include "WindowSurfaceWaylandMultiBuffer.h"
14 #include "gfx2DGlue.h"
15 #include "gfxPlatform.h"
16 #include "MozContainer.h"
17 #include "GtkCompositorWidget.h"
18 #include "mozilla/gfx/DataSurfaceHelpers.h"
19 #include "mozilla/gfx/Tools.h"
20 #include "mozilla/ScopeExit.h"
21 #include "mozilla/StaticPrefs_widget.h"
22 #include "mozilla/WidgetUtils.h"
26 # include "mozilla/Logging.h"
28 extern mozilla::LazyLogModule gWidgetWaylandLog
;
29 # define LOGWAYLAND(...) \
30 MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
32 # define LOGWAYLAND(...)
33 #endif /* MOZ_LOGGING */
35 namespace mozilla::widget
{
38 Wayland multi-thread rendering scheme
40 Every rendering thread (main thread, compositor thread) contains its own
41 nsWaylandDisplay object connected to Wayland compositor (Mutter, Weston, etc.)
43 WindowSurfaceWayland implements WindowSurface class and draws nsWindow by
44 WindowSurface interface (Lock, Commit) to screen through nsWaylandDisplay.
46 ----------------------
47 | Wayland compositor |
48 ----------------------
51 ----------------------
53 ----------------------
57 | --------------------------------- ------------------
58 | | WindowSurfaceWayland |<------>| nsWindow |
59 | | | ------------------
60 | | ----------------------- |
61 | | | WaylandBufferSHM | |
63 | | | ------------------- | |
64 | | | | WaylandShmPool | | |
65 | | | ------------------- | |
66 | | ----------------------- |
68 | | ----------------------- |
69 | | | WaylandBufferSHM | |
71 | | | ------------------- | |
72 | | | | WaylandShmPool | | |
73 | | | ------------------- | |
74 | | ----------------------- |
75 | ---------------------------------
78 --------------------------------- ------------------
79 | WindowSurfaceWayland |<------>| nsWindow |
80 | | ------------------
81 | ----------------------- |
82 | | WaylandBufferSHM | |
84 | | ------------------- | |
85 | | | WaylandShmPool | | |
86 | | ------------------- | |
87 | ----------------------- |
89 | ----------------------- |
90 | | WaylandBufferSHM | |
92 | | ------------------- | |
93 | | | WaylandShmPool | | |
94 | | ------------------- | |
95 | ----------------------- |
96 ---------------------------------
101 Is our connection to Wayland display server,
102 holds our display connection (wl_display) and event queue (wl_event_queue).
104 nsWaylandDisplay is created for every thread which sends data to Wayland
105 compositor. Wayland events for main thread is served by default Gtk+ loop,
106 for other threads (compositor) we must create wl_event_queue and run event loop.
111 Is a Wayland implementation of WindowSurface class for WindowSurfaceProvider,
112 we implement Lock() and Commit() interfaces from WindowSurface
115 One WindowSurfaceWayland draws one nsWindow so those are tied 1:1.
116 At Wayland level it holds one wl_surface object.
118 To perform visualiation of nsWindow, WindowSurfaceWayland contains one
119 wl_surface and two wl_buffer objects (owned by WaylandBufferSHM)
120 as we use double buffering. When nsWindow drawing is finished to wl_buffer,
121 the wl_buffer is attached to wl_surface and it's sent to Wayland compositor.
123 When there's no wl_buffer available for drawing (all wl_buffers are locked in
124 compositor for instance) we store the drawing to WindowImageSurface object
125 and draw later when wl_buffer becomes available or discard the
126 WindowImageSurface cache when whole screen is invalidated.
130 Is a class which provides a wl_buffer for drawing.
131 Wl_buffer is a main Wayland object with actual graphics data.
132 Wl_buffer basically represent one complete window screen.
133 When double buffering is involved every window (GdkWindow for instance)
134 utilises two wl_buffers which are cycled. One is filed with data by application
135 and one is rendered by compositor.
137 WaylandBufferSHM is implemented by shared memory (shm).
138 It owns wl_buffer object, owns WaylandShmPool
139 (which provides the shared memory) and ties them together.
143 WaylandShmPool acts as a manager of shared memory for WaylandBufferSHM.
144 Allocates it, holds reference to it and releases it.
146 We allocate shared memory (shm) by mmap(..., MAP_SHARED,...) as an interface
147 between us and wayland compositor. We draw our graphics data to the shm and
148 handle to wayland compositor by WaylandBufferSHM/WindowSurfaceWayland
149 (wl_buffer/wl_surface).
152 using gfx::DataSourceSurface
;
154 #define BACK_BUFFER_NUM 3
156 WindowSurfaceWaylandMB::WindowSurfaceWaylandMB(
157 RefPtr
<nsWindow
> aWindow
, GtkCompositorWidget
* aCompositorWidget
)
158 : mSurfaceLock("WindowSurfaceWayland lock"),
159 mWindow(std::move(aWindow
)),
160 mCompositorWidget(aCompositorWidget
),
161 mFrameInProcess(false),
162 mCallbackRequested(false) {}
164 bool WindowSurfaceWaylandMB::MaybeUpdateWindowSize() {
165 // We want to get window size from compositor widget as it matches window
166 // size used by parent RenderCompositorSWGL rendrer.
167 // For main thread rendering mCompositorWidget is not available so get
168 // window size directly from nsWindow.
169 LayoutDeviceIntSize newWindowSize
= mCompositorWidget
170 ? mCompositorWidget
->GetClientSize()
171 : mWindow
->GetClientSize();
172 if (mWindowSize
!= newWindowSize
) {
173 mWindowSize
= newWindowSize
;
179 already_AddRefed
<DrawTarget
> WindowSurfaceWaylandMB::Lock(
180 const LayoutDeviceIntRegion
& aInvalidRegion
) {
181 MutexAutoLock
lock(mSurfaceLock
);
184 gfx::IntRect lockRect
= aInvalidRegion
.GetBounds().ToUnknownRect();
185 LOGWAYLAND("WindowSurfaceWaylandMB::Lock [%p] [%d,%d] -> [%d x %d] rects %d",
186 (void*)mWindow
.get(), lockRect
.x
, lockRect
.y
, lockRect
.width
,
187 lockRect
.height
, aInvalidRegion
.GetNumRects());
190 if (mWindow
->GetWindowType() == WindowType::Invisible
) {
193 mFrameInProcess
= true;
195 CollectPendingSurfaces(lock
);
197 if (MaybeUpdateWindowSize()) {
198 LOGWAYLAND(" new window size [%d x %d]", mWindowSize
.width
,
200 if (mInProgressBuffer
) {
201 ReturnBufferToPool(lock
, mInProgressBuffer
);
202 mInProgressBuffer
= nullptr;
205 ReturnBufferToPool(lock
, mFrontBuffer
);
206 mFrontBuffer
= nullptr;
208 mAvailableBuffers
.Clear();
211 if (!mInProgressBuffer
) {
212 if (mFrontBuffer
&& !mFrontBuffer
->IsAttached()) {
213 mInProgressBuffer
= mFrontBuffer
;
215 mInProgressBuffer
= ObtainBufferFromPool(lock
, mWindowSize
);
216 if (!mInProgressBuffer
) {
220 HandlePartialUpdate(lock
, aInvalidRegion
);
221 ReturnBufferToPool(lock
, mFrontBuffer
);
224 mFrontBuffer
= nullptr;
225 mFrontBufferInvalidRegion
.SetEmpty();
228 RefPtr
<DrawTarget
> dt
= mInProgressBuffer
->Lock();
232 void WindowSurfaceWaylandMB::HandlePartialUpdate(
233 const MutexAutoLock
& aProofOfLock
,
234 const LayoutDeviceIntRegion
& aInvalidRegion
) {
235 LayoutDeviceIntRegion copyRegion
;
236 if (mInProgressBuffer
->GetBufferAge() == 2) {
237 copyRegion
.Sub(mFrontBufferInvalidRegion
, aInvalidRegion
);
239 LayoutDeviceIntSize frontBufferSize
= mFrontBuffer
->GetSize();
240 copyRegion
= LayoutDeviceIntRegion(LayoutDeviceIntRect(
241 0, 0, frontBufferSize
.width
, frontBufferSize
.height
));
242 copyRegion
.SubOut(aInvalidRegion
);
245 if (!copyRegion
.IsEmpty()) {
246 RefPtr
<DataSourceSurface
> dataSourceSurface
=
247 mozilla::gfx::CreateDataSourceSurfaceFromData(
248 mFrontBuffer
->GetSize().ToUnknownSize(),
249 mFrontBuffer
->GetSurfaceFormat(),
250 (const uint8_t*)mFrontBuffer
->GetShmPool()->GetImageData(),
251 mFrontBuffer
->GetSize().width
*
252 BytesPerPixel(mFrontBuffer
->GetSurfaceFormat()));
253 RefPtr
<DrawTarget
> dt
= mInProgressBuffer
->Lock();
255 for (auto iter
= copyRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
256 LayoutDeviceIntRect r
= iter
.Get();
257 dt
->CopySurface(dataSourceSurface
, r
.ToUnknownRect(),
258 gfx::IntPoint(r
.x
, r
.y
));
263 void WindowSurfaceWaylandMB::Commit(
264 const LayoutDeviceIntRegion
& aInvalidRegion
) {
265 MutexAutoLock
lock(mSurfaceLock
);
266 Commit(lock
, aInvalidRegion
);
269 void WindowSurfaceWaylandMB::Commit(
270 const MutexAutoLock
& aProofOfLock
,
271 const LayoutDeviceIntRegion
& aInvalidRegion
) {
273 gfx::IntRect invalidRect
= aInvalidRegion
.GetBounds().ToUnknownRect();
275 "WindowSurfaceWaylandMB::Commit [%p] damage rect [%d, %d] -> [%d x %d] "
276 "Window [%d x %d]\n",
277 (void*)mWindow
.get(), invalidRect
.x
, invalidRect
.y
, invalidRect
.width
,
278 invalidRect
.height
, mWindowSize
.width
, mWindowSize
.height
);
281 if (!mInProgressBuffer
) {
285 mFrameInProcess
= false;
287 MozContainer
* container
= mWindow
->GetMozContainer();
288 MozContainerSurfaceLock
MozContainerLock(container
);
289 struct wl_surface
* waylandSurface
= MozContainerLock
.GetSurface();
290 if (!waylandSurface
) {
292 "WindowSurfaceWaylandMB::Commit [%p] frame queued: can't lock "
294 (void*)mWindow
.get());
295 if (!mCallbackRequested
) {
296 RefPtr
<WindowSurfaceWaylandMB
> self(this);
297 moz_container_wayland_add_initial_draw_callback_locked(
298 container
, [self
, aInvalidRegion
]() -> void {
299 MutexAutoLock
lock(self
->mSurfaceLock
);
300 if (!self
->mFrameInProcess
) {
301 self
->Commit(lock
, aInvalidRegion
);
303 self
->mCallbackRequested
= false;
305 mCallbackRequested
= true;
310 if (moz_container_wayland_is_commiting_to_parent(container
)) {
311 // When committing to parent surface we must use wl_surface_damage().
312 // A parent surface is created as v.3 object which does not support
313 // wl_surface_damage_buffer().
314 wl_surface_damage(waylandSurface
, 0, 0, INT32_MAX
, INT32_MAX
);
316 for (auto iter
= aInvalidRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
317 LayoutDeviceIntRect r
= iter
.Get();
318 wl_surface_damage_buffer(waylandSurface
, r
.x
, r
.y
, r
.width
, r
.height
);
322 // aProofOfLock is a kind of substitution of MozContainerSurfaceLock.
323 // MozContainer is locked but MozContainerSurfaceLock doen't convert to
324 // MutexAutoLock& so we use aProofOfLock here.
325 moz_container_wayland_set_scale_factor_locked(
326 aProofOfLock
, container
, mWindow
->GdkCeiledScaleFactor());
328 // It's possible that scale factor changed between Lock() and Commit()
329 // but window size is the same.
330 // Don't attach such buffer as it may have incorrect size,
331 // we'll paint new content soon.
332 if (moz_container_wayland_size_matches_scale_factor_locked(
333 aProofOfLock
, container
, mWindowSize
.width
, mWindowSize
.height
)) {
334 mInProgressBuffer
->AttachAndCommit(waylandSurface
);
337 mInProgressBuffer
->ResetBufferAge();
338 mFrontBuffer
= mInProgressBuffer
;
339 mFrontBufferInvalidRegion
= aInvalidRegion
;
340 mInProgressBuffer
= nullptr;
342 EnforcePoolSizeLimit(aProofOfLock
);
343 IncrementBufferAge(aProofOfLock
);
345 if (wl_display_flush(WaylandDisplayGet()->GetDisplay()) == -1) {
346 LOGWAYLAND("WindowSurfaceWaylandMB::Commit [%p] flush failed\n",
347 (void*)mWindow
.get());
351 RefPtr
<WaylandBufferSHM
> WindowSurfaceWaylandMB::ObtainBufferFromPool(
352 const MutexAutoLock
& aProofOfLock
, const LayoutDeviceIntSize
& aSize
) {
353 if (!mAvailableBuffers
.IsEmpty()) {
354 RefPtr
<WaylandBufferSHM
> buffer
= mAvailableBuffers
.PopLastElement();
355 mInUseBuffers
.AppendElement(buffer
);
359 RefPtr
<WaylandBufferSHM
> buffer
= WaylandBufferSHM::Create(aSize
);
361 mInUseBuffers
.AppendElement(buffer
);
367 void WindowSurfaceWaylandMB::ReturnBufferToPool(
368 const MutexAutoLock
& aProofOfLock
,
369 const RefPtr
<WaylandBufferSHM
>& aBuffer
) {
370 if (aBuffer
->IsAttached()) {
371 mPendingBuffers
.AppendElement(aBuffer
);
372 } else if (aBuffer
->IsMatchingSize(mWindowSize
)) {
373 mAvailableBuffers
.AppendElement(aBuffer
);
375 mInUseBuffers
.RemoveElement(aBuffer
);
378 void WindowSurfaceWaylandMB::EnforcePoolSizeLimit(
379 const MutexAutoLock
& aProofOfLock
) {
380 // Enforce the pool size limit, removing least-recently-used entries as
382 while (mAvailableBuffers
.Length() > BACK_BUFFER_NUM
) {
383 mAvailableBuffers
.RemoveElementAt(0);
386 NS_WARNING_ASSERTION(mPendingBuffers
.Length() < BACK_BUFFER_NUM
,
387 "Are we leaking pending buffers?");
388 NS_WARNING_ASSERTION(mInUseBuffers
.Length() < BACK_BUFFER_NUM
,
389 "Are we leaking in-use buffers?");
392 void WindowSurfaceWaylandMB::CollectPendingSurfaces(
393 const MutexAutoLock
& aProofOfLock
) {
394 mPendingBuffers
.RemoveElementsBy([&](auto& buffer
) {
395 if (!buffer
->IsAttached()) {
396 if (buffer
->IsMatchingSize(mWindowSize
)) {
397 mAvailableBuffers
.AppendElement(std::move(buffer
));
405 void WindowSurfaceWaylandMB::IncrementBufferAge(
406 const MutexAutoLock
& aProofOfLock
) {
407 for (const RefPtr
<WaylandBufferSHM
>& buffer
: mInUseBuffers
) {
408 buffer
->IncrementBufferAge();
410 for (const RefPtr
<WaylandBufferSHM
>& buffer
: mPendingBuffers
) {
411 buffer
->IncrementBufferAge();
413 for (const RefPtr
<WaylandBufferSHM
>& buffer
: mAvailableBuffers
) {
414 buffer
->IncrementBufferAge();
418 } // namespace mozilla::widget