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