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/extension_manager.h"
27 #include "ui/gfx/x/x11_switches.h"
28 
29 namespace ui {
30 
31 namespace {
32 
33 constexpr int kMinImageAreaForShmem = 256;
34 
35 // When resizing a segment, the new segment size is calculated as
36 //   new_size = target_size * kShmResizeThreshold
37 // so that target_size has room to grow before another resize is necessary.  We
38 // also want target_size to have room to shrink, so we avoid resizing until
39 //   shrink_size = target_size / kShmResizeThreshold
40 // Given these equations, shrink_size is
41 //   shrink_size = new_size / kShmResizeThreshold ^ 2
42 // new_size is recorded in SoftwareOutputDeviceX11::shm_size_, so we need to
43 // divide by kShmResizeThreshold twice to get the shrink threshold.
44 constexpr float kShmResizeThreshold = 1.5f;
45 constexpr float kShmResizeShrinkThreshold =
46     1.0f / (kShmResizeThreshold * kShmResizeThreshold);
47 
MaxShmSegmentSizeImpl()48 std::size_t MaxShmSegmentSizeImpl() {
49 #if defined(OS_BSD)
50   return base::SysInfo::MaxSharedMemorySize();
51 #else
52   struct shminfo info;
53   if (shmctl(0, IPC_INFO, reinterpret_cast<struct shmid_ds*>(&info)) == -1)
54     return 0;
55   return info.shmmax;
56 #endif
57 }
58 
MaxShmSegmentSize()59 std::size_t MaxShmSegmentSize() {
60   static std::size_t max_size = MaxShmSegmentSizeImpl();
61   return max_size;
62 }
63 
64 #if !defined(OS_CHROMEOS)
IsRemoteHost(const std::string & name)65 bool IsRemoteHost(const std::string& name) {
66   if (name.empty())
67     return false;
68 
69   return !net::HostStringIsLocalhost(name);
70 }
71 
ShouldUseMitShm(x11::Connection * connection)72 bool ShouldUseMitShm(x11::Connection* connection) {
73   // MIT-SHM may be available on remote connetions, but it will be unusable.  Do
74   // a best-effort check to see if the host is remote to disable the SHM
75   // codepath.  It may be possible in contrived cases for there to be a
76   // false-positive, but in that case we'll just fallback to the non-SHM
77   // codepath.
78   auto host = connection->GetConnectionHostname();
79   if (!host.empty() && IsRemoteHost(host))
80     return false;
81 
82   std::unique_ptr<base::Environment> env = base::Environment::Create();
83 
84   // Used by QT.
85   if (env->HasVar("QT_X11_NO_MITSHM"))
86     return false;
87 
88   // Used by JRE.
89   std::string j2d_use_mitshm;
90   if (env->GetVar("J2D_USE_MITSHM", &j2d_use_mitshm) &&
91       (j2d_use_mitshm == "0" ||
92        base::LowerCaseEqualsASCII(j2d_use_mitshm, "false"))) {
93     return false;
94   }
95 
96   // Used by GTK.
97   if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoXshm))
98     return false;
99 
100   return true;
101 }
102 #endif
103 
104 }  // namespace
105 
106 XShmImagePool::FrameState::FrameState() = default;
107 
108 XShmImagePool::FrameState::~FrameState() = default;
109 
110 XShmImagePool::SwapClosure::SwapClosure() = default;
111 
112 XShmImagePool::SwapClosure::~SwapClosure() = default;
113 
XShmImagePool(x11::Connection * connection,x11::Drawable drawable,x11::VisualId visual,int depth,std::size_t frames_pending,bool enable_multibuffering)114 XShmImagePool::XShmImagePool(x11::Connection* connection,
115                              x11::Drawable drawable,
116                              x11::VisualId visual,
117                              int depth,
118                              std::size_t frames_pending,
119                              bool enable_multibuffering)
120     : connection_(connection),
121       drawable_(drawable),
122       visual_(visual),
123       depth_(depth),
124       enable_multibuffering_(enable_multibuffering),
125       frame_states_(frames_pending) {
126   if (enable_multibuffering_)
127     X11EventSource::GetInstance()->AddXEventDispatcher(this);
128 }
129 
~XShmImagePool()130 XShmImagePool::~XShmImagePool() {
131   Cleanup();
132   if (enable_multibuffering_)
133     X11EventSource::GetInstance()->RemoveXEventDispatcher(this);
134 }
135 
Resize(const gfx::Size & pixel_size)136 bool XShmImagePool::Resize(const gfx::Size& pixel_size) {
137   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
138 
139   if (pixel_size == pixel_size_)
140     return true;
141 
142   auto cleanup_fn = [](XShmImagePool* x) { x->Cleanup(); };
143   std::unique_ptr<XShmImagePool, decltype(cleanup_fn)> cleanup{this,
144                                                                cleanup_fn};
145 
146 #if !defined(OS_CHROMEOS)
147   if (!ShouldUseMitShm(connection_))
148     return false;
149 #endif
150 
151   if (!ui::QueryShmSupport())
152     return false;
153 
154   if (pixel_size.width() <= 0 || pixel_size.height() <= 0 ||
155       pixel_size.GetArea() <= kMinImageAreaForShmem) {
156     return false;
157   }
158 
159   SkColorType color_type = ColorTypeForVisual(visual_);
160   if (color_type == kUnknown_SkColorType)
161     return false;
162 
163   SkImageInfo image_info = SkImageInfo::Make(
164       pixel_size.width(), pixel_size.height(), color_type, kPremul_SkAlphaType);
165   std::size_t needed_frame_bytes = image_info.computeMinByteSize();
166 
167   if (needed_frame_bytes > frame_bytes_ ||
168       needed_frame_bytes < frame_bytes_ * kShmResizeShrinkThreshold) {
169     // Resize.
170     Cleanup();
171 
172     frame_bytes_ = needed_frame_bytes * kShmResizeThreshold;
173     if (MaxShmSegmentSize() > 0 && frame_bytes_ > MaxShmSegmentSize()) {
174       if (MaxShmSegmentSize() >= needed_frame_bytes)
175         frame_bytes_ = MaxShmSegmentSize();
176       else
177         return false;
178     }
179 
180     for (FrameState& state : frame_states_) {
181       state.shmid =
182           shmget(IPC_PRIVATE, frame_bytes_,
183                  IPC_CREAT | SHM_R | SHM_W | (SHM_R >> 6) | (SHM_W >> 6));
184       if (state.shmid < 0)
185         return false;
186       state.shmaddr = reinterpret_cast<char*>(shmat(state.shmid, nullptr, 0));
187       if (state.shmaddr == reinterpret_cast<char*>(-1)) {
188         shmctl(state.shmid, IPC_RMID, nullptr);
189         return false;
190       }
191 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
192       // On Linux, a shmid can still be attached after IPC_RMID if otherwise
193       // kept alive.  Detach before XShmAttach to prevent a memory leak in case
194       // the process dies.
195       shmctl(state.shmid, IPC_RMID, nullptr);
196 #endif
197       DCHECK(!state.shmem_attached_to_server);
198       auto shmseg = connection_->GenerateId<x11::Shm::Seg>();
199       auto req = connection_->shm().Attach({
200           .shmseg = shmseg,
201           .shmid = state.shmid,
202           // If this class ever needs to use XShmGetImage(), this needs to be
203           // changed to read-write.
204           .read_only = true,
205       });
206       if (req.Sync().error)
207         return false;
208       state.shmseg = shmseg;
209       state.shmem_attached_to_server = true;
210 #if !defined(OS_LINUX) && !defined(OS_CHROMEOS)
211       // The Linux-specific shmctl behavior above may not be portable, so we're
212       // forced to do IPC_RMID after the server has attached to the segment.
213       shmctl(state.shmid, IPC_RMID, nullptr);
214 #endif
215     }
216   }
217 
218   for (FrameState& state : frame_states_) {
219     state.bitmap = SkBitmap();
220     if (!state.bitmap.installPixels(image_info, state.shmaddr,
221                                     image_info.minRowBytes())) {
222       return false;
223     }
224     state.canvas = std::make_unique<SkCanvas>(state.bitmap);
225   }
226 
227   pixel_size_ = pixel_size;
228   cleanup.release();
229   ready_ = true;
230   return true;
231 }
232 
Ready()233 bool XShmImagePool::Ready() {
234   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
235   return ready_;
236 }
237 
CurrentBitmap()238 SkBitmap& XShmImagePool::CurrentBitmap() {
239   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
240 
241   return frame_states_[current_frame_index_].bitmap;
242 }
243 
CurrentCanvas()244 SkCanvas* XShmImagePool::CurrentCanvas() {
245   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
246 
247   return frame_states_[current_frame_index_].canvas.get();
248 }
249 
CurrentSegment()250 x11::Shm::Seg XShmImagePool::CurrentSegment() {
251   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
252 
253   return frame_states_[current_frame_index_].shmseg;
254 }
255 
SwapBuffers(base::OnceCallback<void (const gfx::Size &)> callback)256 void XShmImagePool::SwapBuffers(
257     base::OnceCallback<void(const gfx::Size&)> callback) {
258   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
259   DCHECK(enable_multibuffering_);
260 
261   swap_closures_.emplace_back();
262   SwapClosure& swap_closure = swap_closures_.back();
263   swap_closure.closure = base::BindOnce(std::move(callback), pixel_size_);
264   swap_closure.shmseg = frame_states_[current_frame_index_].shmseg;
265 
266   current_frame_index_ = (current_frame_index_ + 1) % frame_states_.size();
267 }
268 
DispatchShmCompletionEvent(x11::Shm::CompletionEvent event)269 void XShmImagePool::DispatchShmCompletionEvent(
270     x11::Shm::CompletionEvent event) {
271   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
272   DCHECK_EQ(event.offset, 0UL);
273   DCHECK(enable_multibuffering_);
274 
275   for (auto it = swap_closures_.begin(); it != swap_closures_.end(); ++it) {
276     if (event.shmseg == it->shmseg) {
277       std::move(it->closure).Run();
278       swap_closures_.erase(it);
279       return;
280     }
281   }
282 }
283 
DispatchXEvent(x11::Event * xev)284 bool XShmImagePool::DispatchXEvent(x11::Event* xev) {
285   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
286   DCHECK(enable_multibuffering_);
287 
288   auto* completion = xev->As<x11::Shm::CompletionEvent>();
289   if (!completion || completion->drawable.value != drawable_.value)
290     return false;
291 
292   DispatchShmCompletionEvent(*completion);
293   return true;
294 }
295 
Cleanup()296 void XShmImagePool::Cleanup() {
297   for (FrameState& state : frame_states_) {
298     if (state.shmaddr)
299       shmdt(state.shmaddr);
300     if (state.shmem_attached_to_server)
301       connection_->shm().Detach({state.shmseg});
302     state.shmem_attached_to_server = false;
303     state.shmseg = x11::Shm::Seg{};
304     state.shmid = 0;
305     state.shmaddr = nullptr;
306   }
307   frame_bytes_ = 0;
308   pixel_size_ = gfx::Size();
309   current_frame_index_ = 0;
310   ready_ = false;
311 }
312 
313 }  // namespace ui
314