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