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 "WaylandShmBuffer.h"
8 
9 #include <sys/mman.h>
10 #include <fcntl.h>
11 #include <errno.h>
12 
13 #include "gfx2DGlue.h"
14 #include "gfxPlatform.h"
15 #include "mozilla/WidgetUtils.h"  // For WidgetUtils
16 #include "mozilla/gfx/Tools.h"
17 #include "nsPrintfCString.h"
18 #include "prenv.h"  // For PR_GetEnv
19 
20 #ifdef MOZ_LOGGING
21 #  include "mozilla/Logging.h"
22 #  include "mozilla/ScopeExit.h"
23 #  include "Units.h"
24 extern mozilla::LazyLogModule gWidgetWaylandLog;
25 #  define LOGWAYLAND(args) \
26     MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, args)
27 #else
28 #  define LOGWAYLAND(args)
29 #endif /* MOZ_LOGGING */
30 
31 namespace mozilla::widget {
32 
33 #define BUFFER_BPP 4
34 gfx::SurfaceFormat WaylandShmBuffer::mFormat = gfx::SurfaceFormat::B8G8R8A8;
35 
36 #ifdef MOZ_LOGGING
37 int WaylandShmBuffer::mDumpSerial =
38     PR_GetEnv("MOZ_WAYLAND_DUMP_WL_BUFFERS") ? 1 : 0;
39 char* WaylandShmBuffer::mDumpDir = PR_GetEnv("MOZ_WAYLAND_DUMP_DIR");
40 #endif
41 
WaylandAllocateShmMemory(int aSize)42 static int WaylandAllocateShmMemory(int aSize) {
43   int fd = -1;
44 
45   nsCString shmPrefix("/");
46   const char* snapName = mozilla::widget::WidgetUtils::GetSnapInstanceName();
47   if (snapName != nullptr) {
48     shmPrefix.AppendPrintf("snap.%s.", snapName);
49   }
50   shmPrefix.Append("wayland.mozilla.ipc");
51 
52   do {
53     static int counter = 0;
54     nsPrintfCString shmName("%s.%d", shmPrefix.get(), counter++);
55     fd = shm_open(shmName.get(), O_CREAT | O_RDWR | O_EXCL, 0600);
56     if (fd >= 0) {
57       // We don't want to use leaked file
58       if (shm_unlink(shmName.get()) != 0) {
59         NS_WARNING("shm_unlink failed");
60         return -1;
61       }
62     }
63   } while (fd < 0 && errno == EEXIST);
64 
65   if (fd < 0) {
66     NS_WARNING(nsPrintfCString("shm_open failed: %s", strerror(errno)).get());
67     return -1;
68   }
69 
70   int ret = 0;
71 #ifdef HAVE_POSIX_FALLOCATE
72   do {
73     ret = posix_fallocate(fd, 0, aSize);
74   } while (ret == EINTR);
75   if (ret == 0) {
76     return fd;
77   }
78   if (ret != ENODEV && ret != EINVAL && ret != EOPNOTSUPP) {
79     NS_WARNING(
80         nsPrintfCString("posix_fallocate() fails to allocate shm memory: %s",
81                         strerror(ret))
82             .get());
83     close(fd);
84     return -1;
85   }
86 #endif
87   do {
88     ret = ftruncate(fd, aSize);
89   } while (ret < 0 && errno == EINTR);
90   if (ret < 0) {
91     NS_WARNING(nsPrintfCString("ftruncate() fails to allocate shm memory: %s",
92                                strerror(ret))
93                    .get());
94     close(fd);
95     fd = -1;
96   }
97 
98   return fd;
99 }
100 
101 /* static */
Create(const RefPtr<nsWaylandDisplay> & aWaylandDisplay,int aSize)102 RefPtr<WaylandShmPool> WaylandShmPool::Create(
103     const RefPtr<nsWaylandDisplay>& aWaylandDisplay, int aSize) {
104   RefPtr<WaylandShmPool> shmPool = new WaylandShmPool(aSize);
105 
106   shmPool->mShmPoolFd = WaylandAllocateShmMemory(aSize);
107   if (shmPool->mShmPoolFd < 0) {
108     return nullptr;
109   }
110 
111   shmPool->mImageData = mmap(nullptr, aSize, PROT_READ | PROT_WRITE, MAP_SHARED,
112                              shmPool->mShmPoolFd, 0);
113   if (shmPool->mImageData == MAP_FAILED) {
114     NS_WARNING("Unable to map drawing surface!");
115     return nullptr;
116   }
117 
118   shmPool->mShmPool =
119       wl_shm_create_pool(aWaylandDisplay->GetShm(), shmPool->mShmPoolFd, aSize);
120   if (!shmPool->mShmPool) {
121     return nullptr;
122   }
123 
124   // We set our queue to get mShmPool events at compositor thread.
125   wl_proxy_set_queue((struct wl_proxy*)shmPool->mShmPool,
126                      aWaylandDisplay->GetEventQueue());
127 
128   return shmPool;
129 }
130 
WaylandShmPool(int aSize)131 WaylandShmPool::WaylandShmPool(int aSize)
132     : mShmPool(nullptr),
133       mShmPoolFd(-1),
134       mAllocatedSize(aSize),
135       mImageData(nullptr){};
136 
~WaylandShmPool()137 WaylandShmPool::~WaylandShmPool() {
138   if (mImageData != MAP_FAILED) {
139     munmap(mImageData, mAllocatedSize);
140     mImageData = MAP_FAILED;
141   }
142   g_clear_pointer(&mShmPool, wl_shm_pool_destroy);
143   if (mShmPoolFd >= 0) {
144     close(mShmPoolFd);
145     mShmPoolFd = -1;
146   }
147 }
148 
149 static const struct wl_buffer_listener sBufferListenerWaylandShmBuffer = {
150     WaylandShmBuffer::BufferReleaseCallbackHandler};
151 
152 /* static */
Create(const RefPtr<nsWaylandDisplay> & aWaylandDisplay,const LayoutDeviceIntSize & aSize)153 RefPtr<WaylandShmBuffer> WaylandShmBuffer::Create(
154     const RefPtr<nsWaylandDisplay>& aWaylandDisplay,
155     const LayoutDeviceIntSize& aSize) {
156   RefPtr<WaylandShmBuffer> buffer = new WaylandShmBuffer(aSize);
157 
158   int size = aSize.width * aSize.height * BUFFER_BPP;
159   buffer->mShmPool = WaylandShmPool::Create(aWaylandDisplay, size);
160   if (!buffer->mShmPool) {
161     return nullptr;
162   }
163 
164   buffer->mWLBuffer = wl_shm_pool_create_buffer(
165       buffer->mShmPool->GetShmPool(), 0, aSize.width, aSize.height,
166       aSize.width * BUFFER_BPP, WL_SHM_FORMAT_ARGB8888);
167   if (!buffer->mWLBuffer) {
168     return nullptr;
169   }
170 
171   wl_proxy_set_queue((struct wl_proxy*)buffer->mWLBuffer,
172                      aWaylandDisplay->GetEventQueue());
173   wl_buffer_add_listener(buffer->mWLBuffer, &sBufferListenerWaylandShmBuffer,
174                          buffer.get());
175 
176   LOGWAYLAND(("WaylandShmBuffer Created [%p] WaylandDisplay [%p]\n",
177               buffer.get(), aWaylandDisplay.get()));
178 
179   return buffer;
180 }
181 
WaylandShmBuffer(const LayoutDeviceIntSize & aSize)182 WaylandShmBuffer::WaylandShmBuffer(const LayoutDeviceIntSize& aSize)
183     : mWLBuffer(nullptr),
184       mBufferReleaseFunc(nullptr),
185       mBufferReleaseData(nullptr),
186       mSize(aSize),
187       mBufferAge(0),
188       mAttached(false) {}
189 
~WaylandShmBuffer()190 WaylandShmBuffer::~WaylandShmBuffer() {
191   g_clear_pointer(&mWLBuffer, wl_buffer_destroy);
192 }
193 
Lock()194 already_AddRefed<gfx::DrawTarget> WaylandShmBuffer::Lock() {
195   return gfxPlatform::CreateDrawTargetForData(
196       static_cast<unsigned char*>(mShmPool->GetImageData()),
197       mSize.ToUnknownSize(), BUFFER_BPP * mSize.width, GetSurfaceFormat());
198 }
199 
AttachAndCommit(wl_surface * aSurface)200 void WaylandShmBuffer::AttachAndCommit(wl_surface* aSurface) {
201   LOGWAYLAND(
202       ("WaylandShmBuffer::AttachAndCommit [%p] wl_surface %p ID %d wl_buffer "
203        "%p ID %d\n",
204        (void*)this, (void*)aSurface,
205        aSurface ? wl_proxy_get_id((struct wl_proxy*)aSurface) : -1,
206        (void*)GetWlBuffer(),
207        GetWlBuffer() ? wl_proxy_get_id((struct wl_proxy*)GetWlBuffer()) : -1));
208 
209   wl_buffer* buffer = GetWlBuffer();
210   if (buffer) {
211     mAttached = true;
212     wl_surface_attach(aSurface, buffer, 0, 0);
213     wl_surface_commit(aSurface);
214   }
215 }
216 
Clear()217 void WaylandShmBuffer::Clear() {
218   memset(mShmPool->GetImageData(), 0, mSize.height * mSize.width * BUFFER_BPP);
219 }
220 
BufferReleaseCallbackHandler(wl_buffer * aBuffer)221 void WaylandShmBuffer::BufferReleaseCallbackHandler(wl_buffer* aBuffer) {
222   mAttached = false;
223 
224   if (mBufferReleaseFunc) {
225     mBufferReleaseFunc(mBufferReleaseData, aBuffer);
226   }
227 }
228 
BufferReleaseCallbackHandler(void * aData,wl_buffer * aBuffer)229 void WaylandShmBuffer::BufferReleaseCallbackHandler(void* aData,
230                                                     wl_buffer* aBuffer) {
231   auto* shmBuffer = reinterpret_cast<WaylandShmBuffer*>(aData);
232   shmBuffer->BufferReleaseCallbackHandler(aBuffer);
233 }
234 
235 #ifdef MOZ_LOGGING
DumpToFile(const char * aHint)236 void WaylandShmBuffer::DumpToFile(const char* aHint) {
237   if (!mDumpSerial) {
238     return;
239   }
240 
241   cairo_surface_t* surface = nullptr;
242   auto unmap = MakeScopeExit([&] {
243     if (surface) {
244       cairo_surface_destroy(surface);
245     }
246   });
247   surface = cairo_image_surface_create_for_data(
248       (unsigned char*)mShmPool->GetImageData(), CAIRO_FORMAT_ARGB32,
249       mSize.width, mSize.height, BUFFER_BPP * mSize.width);
250   if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
251     nsCString filename;
252     if (mDumpDir) {
253       filename.Append(mDumpDir);
254       filename.Append('/');
255     }
256     filename.Append(
257         nsPrintfCString("firefox-wl-buffer-%.5d-%s.png", mDumpSerial++, aHint));
258     cairo_surface_write_to_png(surface, filename.get());
259     LOGWAYLAND(("Dumped wl_buffer to %s\n", filename.get()));
260   }
261 }
262 #endif
263 
264 }  // namespace mozilla::widget
265