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