1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/base/x/x11_shm_image_pool.h"
6
7 #include <sys/ipc.h>
8 #include <sys/shm.h>
9
10 #include <memory>
11 #include <utility>
12
13 #include "base/bind.h"
14 #include "base/callback.h"
15 #include "base/command_line.h"
16 #include "base/environment.h"
17 #include "base/location.h"
18 #include "base/strings/string_util.h"
19 #include "base/system/sys_info.h"
20 #include "base/threading/thread_task_runner_handle.h"
21 #include "build/build_config.h"
22 #include "net/base/url_util.h"
23 #include "ui/events/platform/platform_event_dispatcher.h"
24 #include "ui/events/platform/platform_event_source.h"
25 #include "ui/gfx/geometry/rect.h"
26 #include "ui/gfx/x/x11_switches.h"
27
28 namespace ui {
29
30 namespace {
31
32 constexpr int kMinImageAreaForShmem = 256;
33
34 // When resizing a segment, the new segment size is calculated as
35 // new_size = target_size * kShmResizeThreshold
36 // so that target_size has room to grow before another resize is necessary. We
37 // also want target_size to have room to shrink, so we avoid resizing until
38 // shrink_size = target_size / kShmResizeThreshold
39 // Given these equations, shrink_size is
40 // shrink_size = new_size / kShmResizeThreshold ^ 2
41 // new_size is recorded in SoftwareOutputDeviceX11::shm_size_, so we need to
42 // divide by kShmResizeThreshold twice to get the shrink threshold.
43 constexpr float kShmResizeThreshold = 1.5f;
44 constexpr float kShmResizeShrinkThreshold =
45 1.0f / (kShmResizeThreshold * kShmResizeThreshold);
46
MaxShmSegmentSizeImpl()47 std::size_t MaxShmSegmentSizeImpl() {
48 #if defined(OS_BSD)
49 return base::SysInfo::MaxSharedMemorySize();
50 #else
51 struct shminfo info;
52 if (shmctl(0, IPC_INFO, reinterpret_cast<struct shmid_ds*>(&info)) == -1)
53 return 0;
54 return info.shmmax;
55 #endif
56 }
57
MaxShmSegmentSize()58 std::size_t MaxShmSegmentSize() {
59 static std::size_t max_size = MaxShmSegmentSizeImpl();
60 return max_size;
61 }
62
63 #if !defined(OS_CHROMEOS)
IsRemoteHost(const std::string & name)64 bool IsRemoteHost(const std::string& name) {
65 if (name.empty())
66 return false;
67
68 return !net::HostStringIsLocalhost(name);
69 }
70
ShouldUseMitShm(XDisplay * display)71 bool ShouldUseMitShm(XDisplay* display) {
72 // MIT-SHM may be available on remote connetions, but it will be unusable. Do
73 // a best-effort check to see if the host is remote to disable the SHM
74 // codepath. It may be possible in contrived cases for there to be a
75 // false-positive, but in that case we'll just fallback to the non-SHM
76 // codepath.
77 char* display_string = DisplayString(display);
78 char* host = nullptr;
79 int display_id = 0;
80 int screen = 0;
81 if (xcb_parse_display(display_string, &host, &display_id, &screen)) {
82 std::string name = host;
83 free(host);
84 if (IsRemoteHost(name))
85 return false;
86 }
87
88 std::unique_ptr<base::Environment> env = base::Environment::Create();
89
90 // Used by QT.
91 if (env->HasVar("QT_X11_NO_MITSHM"))
92 return false;
93
94 // Used by JRE.
95 std::string j2d_use_mitshm;
96 if (env->GetVar("J2D_USE_MITSHM", &j2d_use_mitshm) &&
97 (j2d_use_mitshm == "0" ||
98 base::LowerCaseEqualsASCII(j2d_use_mitshm, "false"))) {
99 return false;
100 }
101
102 // Used by GTK.
103 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoXshm))
104 return false;
105
106 return true;
107 }
108 #endif
109
110 } // namespace
111
112 XShmImagePool::FrameState::FrameState() = default;
113
114 XShmImagePool::FrameState::~FrameState() = default;
115
116 XShmImagePool::SwapClosure::SwapClosure() = default;
117
118 XShmImagePool::SwapClosure::~SwapClosure() = default;
119
XShmImagePool(scoped_refptr<base::SequencedTaskRunner> host_task_runner,scoped_refptr<base::SequencedTaskRunner> event_task_runner,XDisplay * display,XID drawable,Visual * visual,int depth,std::size_t frames_pending)120 XShmImagePool::XShmImagePool(
121 scoped_refptr<base::SequencedTaskRunner> host_task_runner,
122 scoped_refptr<base::SequencedTaskRunner> event_task_runner,
123 XDisplay* display,
124 XID drawable,
125 Visual* visual,
126 int depth,
127 std::size_t frames_pending)
128 : host_task_runner_(std::move(host_task_runner)),
129 event_task_runner_(std::move(event_task_runner)),
130 display_(display),
131 drawable_(drawable),
132 visual_(visual),
133 depth_(depth),
134 frame_states_(frames_pending) {
135 DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
136 }
137
Resize(const gfx::Size & pixel_size)138 bool XShmImagePool::Resize(const gfx::Size& pixel_size) {
139 DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
140
141 if (pixel_size == pixel_size_)
142 return true;
143
144 auto cleanup_fn = [](XShmImagePool* x) { x->Cleanup(); };
145 std::unique_ptr<XShmImagePool, decltype(cleanup_fn)> cleanup{this,
146 cleanup_fn};
147
148 if (!event_task_runner_)
149 return false;
150
151 #if !defined(OS_CHROMEOS)
152 if (!ShouldUseMitShm(display_))
153 return false;
154 #endif
155
156 if (!ui::QueryShmSupport())
157 return false;
158
159 if (pixel_size.width() <= 0 || pixel_size.height() <= 0 ||
160 pixel_size.GetArea() <= kMinImageAreaForShmem) {
161 return false;
162 }
163
164 SkColorType color_type = ColorTypeForVisual(visual_);
165 if (color_type == kUnknown_SkColorType)
166 return false;
167
168 std::size_t needed_frame_bytes;
169 for (std::size_t i = 0; i < frame_states_.size(); ++i) {
170 FrameState& state = frame_states_[i];
171 state.image.reset(XShmCreateImage(display_, visual_, depth_, ZPixmap,
172 nullptr, &state.shminfo_,
173 pixel_size.width(), pixel_size.height()));
174 if (!state.image)
175 return false;
176 std::size_t current_frame_bytes =
177 state.image->bytes_per_line * state.image->height;
178 if (i == 0)
179 needed_frame_bytes = current_frame_bytes;
180 else
181 DCHECK_EQ(current_frame_bytes, needed_frame_bytes);
182 }
183
184 if (needed_frame_bytes > frame_bytes_ ||
185 needed_frame_bytes < frame_bytes_ * kShmResizeShrinkThreshold) {
186 // Resize.
187 Cleanup();
188
189 frame_bytes_ = needed_frame_bytes * kShmResizeThreshold;
190 if (MaxShmSegmentSize() > 0 && frame_bytes_ > MaxShmSegmentSize()) {
191 if (MaxShmSegmentSize() >= needed_frame_bytes)
192 frame_bytes_ = MaxShmSegmentSize();
193 else
194 return false;
195 }
196
197 for (FrameState& state : frame_states_) {
198 state.shminfo_.shmid =
199 shmget(IPC_PRIVATE, frame_bytes_,
200 IPC_CREAT | SHM_R | SHM_W | (SHM_R >> 6) | (SHM_W >> 6));
201 if (state.shminfo_.shmid < 0)
202 return false;
203 state.shminfo_.shmaddr =
204 reinterpret_cast<char*>(shmat(state.shminfo_.shmid, nullptr, 0));
205 if (state.shminfo_.shmaddr == reinterpret_cast<char*>(-1)) {
206 shmctl(state.shminfo_.shmid, IPC_RMID, nullptr);
207 return false;
208 }
209 #if defined(OS_LINUX) || defined(OS_BSD)
210 // On Linux, a shmid can still be attached after IPC_RMID if otherwise
211 // kept alive. Detach before XShmAttach to prevent a memory leak in case
212 // the process dies.
213 shmctl(state.shminfo_.shmid, IPC_RMID, nullptr);
214 #endif
215 DCHECK(!state.shmem_attached_to_server_);
216 if (!XShmAttach(display_, &state.shminfo_))
217 return false;
218 state.shmem_attached_to_server_ = true;
219 #if (!defined(OS_LINUX) && !defined(OS_BSD))
220 // The Linux-specific shmctl behavior above may not be portable, so we're
221 // forced to do IPC_RMID after the server has attached to the segment.
222 // XShmAttach is asynchronous, so we must also sync.
223 XSync(display_, x11::False);
224 shmctl(shminfo_.shmid, IPC_RMID, nullptr);
225 #endif
226 // If this class ever needs to use XShmGetImage(), this needs to be
227 // changed to read-write.
228 state.shminfo_.readOnly = true;
229 }
230 }
231
232 for (FrameState& state : frame_states_) {
233 state.image->data = state.shminfo_.shmaddr;
234 SkImageInfo image_info =
235 SkImageInfo::Make(state.image->width, state.image->height, color_type,
236 kPremul_SkAlphaType);
237 state.bitmap = SkBitmap();
238 if (!state.bitmap.installPixels(image_info, state.image->data,
239 state.image->bytes_per_line)) {
240 return false;
241 }
242 state.canvas = std::make_unique<SkCanvas>(state.bitmap);
243 }
244
245 pixel_size_ = pixel_size;
246 cleanup.release();
247 ready_ = true;
248 return true;
249 }
250
Ready()251 bool XShmImagePool::Ready() {
252 DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
253 return ready_;
254 }
255
CurrentBitmap()256 SkBitmap& XShmImagePool::CurrentBitmap() {
257 DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
258
259 return frame_states_[current_frame_index_].bitmap;
260 }
261
CurrentCanvas()262 SkCanvas* XShmImagePool::CurrentCanvas() {
263 DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
264
265 return frame_states_[current_frame_index_].canvas.get();
266 }
267
CurrentImage()268 XImage* XShmImagePool::CurrentImage() {
269 DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
270
271 return frame_states_[current_frame_index_].image.get();
272 }
273
SwapBuffers(base::OnceCallback<void (const gfx::Size &)> callback)274 void XShmImagePool::SwapBuffers(
275 base::OnceCallback<void(const gfx::Size&)> callback) {
276 DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
277
278 swap_closures_.emplace_back();
279 SwapClosure& swap_closure = swap_closures_.back();
280 swap_closure.closure = base::BindOnce(std::move(callback), pixel_size_);
281 swap_closure.shmseg = frame_states_[current_frame_index_].shminfo_.shmseg;
282
283 current_frame_index_ = (current_frame_index_ + 1) % frame_states_.size();
284 }
285
Initialize()286 void XShmImagePool::Initialize() {
287 DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
288 if (event_task_runner_)
289 event_task_runner_->PostTask(
290 FROM_HERE, base::BindOnce(&XShmImagePool::InitializeOnGpu, this));
291 }
292
Teardown()293 void XShmImagePool::Teardown() {
294 DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
295 if (event_task_runner_)
296 event_task_runner_->PostTask(
297 FROM_HERE, base::BindOnce(&XShmImagePool::TeardownOnGpu, this));
298 }
299
~XShmImagePool()300 XShmImagePool::~XShmImagePool() {
301 Cleanup();
302 #ifndef NDEBUG
303 DCHECK(!dispatcher_registered_);
304 #endif
305 }
306
DispatchShmCompletionEvent(XShmCompletionEvent event)307 void XShmImagePool::DispatchShmCompletionEvent(XShmCompletionEvent event) {
308 DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
309 DCHECK_EQ(event.offset, 0UL);
310
311 for (auto it = swap_closures_.begin(); it != swap_closures_.end(); ++it) {
312 if (event.shmseg == it->shmseg) {
313 std::move(it->closure).Run();
314 swap_closures_.erase(it);
315 return;
316 }
317 }
318 }
319
CanDispatchXEvent(XEvent * xev)320 bool XShmImagePool::CanDispatchXEvent(XEvent* xev) {
321 DCHECK(event_task_runner_->RunsTasksInCurrentSequence());
322
323 if (xev->type != ui::ShmEventBase() + ShmCompletion)
324 return false;
325
326 XShmCompletionEvent* shm_event = reinterpret_cast<XShmCompletionEvent*>(xev);
327 return shm_event->drawable == drawable_;
328 }
329
DispatchXEvent(XEvent * xev)330 bool XShmImagePool::DispatchXEvent(XEvent* xev) {
331 if (!CanDispatchXEvent(xev))
332 return false;
333
334 XShmCompletionEvent* shm_event = reinterpret_cast<XShmCompletionEvent*>(xev);
335 host_task_runner_->PostTask(
336 FROM_HERE, base::BindOnce(&XShmImagePool::DispatchShmCompletionEvent,
337 this, *shm_event));
338 return true;
339 }
340
InitializeOnGpu()341 void XShmImagePool::InitializeOnGpu() {
342 DCHECK(event_task_runner_->RunsTasksInCurrentSequence());
343 X11EventSource::GetInstance()->AddXEventDispatcher(this);
344 #ifndef NDEBUG
345 dispatcher_registered_ = true;
346 #endif
347 }
348
TeardownOnGpu()349 void XShmImagePool::TeardownOnGpu() {
350 DCHECK(event_task_runner_->RunsTasksInCurrentSequence());
351 X11EventSource::GetInstance()->RemoveXEventDispatcher(this);
352 #ifndef NDEBUG
353 dispatcher_registered_ = false;
354 #endif
355 }
356
Cleanup()357 void XShmImagePool::Cleanup() {
358 for (FrameState& state : frame_states_) {
359 if (state.shminfo_.shmaddr)
360 shmdt(state.shminfo_.shmaddr);
361 if (state.shmem_attached_to_server_)
362 XShmDetach(display_, &state.shminfo_);
363 state.shmem_attached_to_server_ = false;
364 state.shminfo_ = {};
365 }
366 frame_bytes_ = 0;
367 pixel_size_ = gfx::Size();
368 current_frame_index_ = 0;
369 ready_ = false;
370 }
371
372 } // namespace ui
373