File: x11_shm_image_pool.cc

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (307 lines) | stat: -rw-r--r-- 9,689 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/base/x/x11_shm_image_pool.h"

#include <sys/ipc.h>
#include <sys/shm.h>

#include <memory>
#include <utility>

#include "base/command_line.h"
#include "base/environment.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "net/base/url_util.h"
#include "ui/events/platform/platform_event_dispatcher.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/switches.h"
#include "ui/gfx/x/extension_manager.h"

namespace ui {

namespace {

constexpr int kMinImageAreaForShmem = 256;

// When resizing a segment, the new segment size is calculated as
//   new_size = target_size * kShmResizeThreshold
// so that target_size has room to grow before another resize is necessary.  We
// also want target_size to have room to shrink, so we avoid resizing until
//   shrink_size = target_size / kShmResizeThreshold
// Given these equations, shrink_size is
//   shrink_size = new_size / kShmResizeThreshold ^ 2
// new_size is recorded in SoftwareOutputDeviceX11::shm_size_, so we need to
// divide by kShmResizeThreshold twice to get the shrink threshold.
constexpr float kShmResizeThreshold = 1.5f;
constexpr float kShmResizeShrinkThreshold =
    1.0f / (kShmResizeThreshold * kShmResizeThreshold);

std::size_t MaxShmSegmentSizeImpl() {
  struct shminfo info;
  if (shmctl(0, IPC_INFO, reinterpret_cast<struct shmid_ds*>(&info)) == -1)
    return 0;
  return info.shmmax;
}

std::size_t MaxShmSegmentSize() {
  static std::size_t max_size = MaxShmSegmentSizeImpl();
  return max_size;
}

#if !BUILDFLAG(IS_CHROMEOS)
bool IsRemoteHost(const std::string& name) {
  if (name.empty())
    return false;

  return !net::HostStringIsLocalhost(name);
}

bool ShouldUseMitShm(x11::Connection* connection) {
  // MIT-SHM may be available on remote connetions, but it will be unusable.  Do
  // a best-effort check to see if the host is remote to disable the SHM
  // codepath.  It may be possible in contrived cases for there to be a
  // false-positive, but in that case we'll just fallback to the non-SHM
  // codepath.
  auto host = connection->GetConnectionHostname();
  if (!host.empty() && IsRemoteHost(host))
    return false;

  std::unique_ptr<base::Environment> env = base::Environment::Create();

  // Used by QT.
  if (env->HasVar("QT_X11_NO_MITSHM"))
    return false;

  // Used by JRE.
  std::optional<std::string> j2d_use_mitshm = env->GetVar("J2D_USE_MITSHM");
  if (j2d_use_mitshm.has_value() &&
      (*j2d_use_mitshm == "0" ||
       base::EqualsCaseInsensitiveASCII(*j2d_use_mitshm, "false"))) {
    return false;
  }

  // Used by GTK.
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoXshm))
    return false;

  return true;
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

}  // namespace

XShmImagePool::FrameState::FrameState() = default;

XShmImagePool::FrameState::~FrameState() = default;

XShmImagePool::SwapClosure::SwapClosure() = default;

XShmImagePool::SwapClosure::~SwapClosure() = default;

XShmImagePool::XShmImagePool(x11::Connection* connection,
                             x11::Drawable drawable,
                             x11::VisualId visual,
                             int depth,
                             std::size_t frames_pending,
                             bool enable_multibuffering)
    : connection_(connection),
      drawable_(drawable),
      visual_(visual),
      depth_(depth),
      enable_multibuffering_(enable_multibuffering),
      frame_states_(frames_pending) {
  if (enable_multibuffering_)
    connection_->AddEventObserver(this);
}

XShmImagePool::~XShmImagePool() {
  Cleanup();
  if (enable_multibuffering_)
    connection_->RemoveEventObserver(this);
}

bool XShmImagePool::Resize(const gfx::Size& pixel_size) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (pixel_size == pixel_size_)
    return true;

  auto cleanup_fn = [](XShmImagePool* x) { x->Cleanup(); };
  std::unique_ptr<XShmImagePool, decltype(cleanup_fn)> cleanup{this,
                                                               cleanup_fn};

#if !BUILDFLAG(IS_CHROMEOS)
  if (!ShouldUseMitShm(connection_))
    return false;
#endif  // !BUILDFLAG(IS_CHROMEOS)

  if (!ui::QueryShmSupport())
    return false;

  if (pixel_size.width() <= 0 || pixel_size.height() <= 0 ||
      pixel_size.GetArea() <= kMinImageAreaForShmem) {
    return false;
  }

  SkColorType color_type = ColorTypeForVisual(visual_);
  if (color_type == kUnknown_SkColorType)
    return false;

  SkImageInfo image_info = SkImageInfo::Make(
      pixel_size.width(), pixel_size.height(), color_type, kPremul_SkAlphaType);
  std::size_t needed_frame_bytes = image_info.computeMinByteSize();

  if (needed_frame_bytes > frame_bytes_ ||
      needed_frame_bytes < frame_bytes_ * kShmResizeShrinkThreshold) {
    // Resize.
    Cleanup();

    frame_bytes_ = needed_frame_bytes * kShmResizeThreshold;
    if (MaxShmSegmentSize() > 0 && frame_bytes_ > MaxShmSegmentSize()) {
      if (MaxShmSegmentSize() >= needed_frame_bytes)
        frame_bytes_ = MaxShmSegmentSize();
      else
        return false;
    }

    for (FrameState& state : frame_states_) {
      state.shmid =
          shmget(IPC_PRIVATE, frame_bytes_,
                 IPC_CREAT | SHM_R | SHM_W | (SHM_R >> 6) | (SHM_W >> 6));
      if (state.shmid < 0)
        return false;
      state.shmaddr = reinterpret_cast<char*>(shmat(state.shmid, nullptr, 0));
      if (state.shmaddr == reinterpret_cast<char*>(-1)) {
        shmctl(state.shmid, IPC_RMID, nullptr);
        return false;
      }
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
      // On Linux, a shmid can still be attached after IPC_RMID if otherwise
      // kept alive.  Detach before XShmAttach to prevent a memory leak in case
      // the process dies.
      shmctl(state.shmid, IPC_RMID, nullptr);
#endif
      DCHECK(!state.shmem_attached_to_server);
      auto shmseg = connection_->GenerateId<x11::Shm::Seg>();
      auto req = connection_->shm().Attach({
          .shmseg = shmseg,
          .shmid = static_cast<uint32_t>(state.shmid),
          // If this class ever needs to use XShmGetImage(), this needs to be
          // changed to read-write.
          .read_only = true,
      });
      if (req.Sync().error)
        return false;
      state.shmseg = shmseg;
      state.shmem_attached_to_server = true;
#if !BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS)
      // The Linux-specific shmctl behavior above may not be portable, so we're
      // forced to do IPC_RMID after the server has attached to the segment.
      shmctl(state.shmid, IPC_RMID, nullptr);
#endif
    }
  }

  const auto* visual_info = connection_->GetVisualInfoFromId(visual_);
  if (!visual_info)
    return false;
  size_t row_bytes = RowBytesForVisualWidth(*visual_info, pixel_size.width());

  for (FrameState& state : frame_states_) {
    state.bitmap = SkBitmap();
    if (!state.bitmap.installPixels(image_info, state.shmaddr, row_bytes))
      return false;
    state.canvas = std::make_unique<SkCanvas>(state.bitmap);
  }

  pixel_size_ = pixel_size;
  cleanup.release();
  ready_ = true;
  return true;
}

bool XShmImagePool::Ready() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return ready_;
}

SkBitmap& XShmImagePool::CurrentBitmap() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  return frame_states_[current_frame_index_].bitmap;
}

SkCanvas* XShmImagePool::CurrentCanvas() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  return frame_states_[current_frame_index_].canvas.get();
}

x11::Shm::Seg XShmImagePool::CurrentSegment() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  return frame_states_[current_frame_index_].shmseg;
}

void XShmImagePool::SwapBuffers(
    base::OnceCallback<void(const gfx::Size&)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(enable_multibuffering_);

  swap_closures_.emplace_back();
  SwapClosure& swap_closure = swap_closures_.back();
  swap_closure.closure = base::BindOnce(std::move(callback), pixel_size_);
  swap_closure.shmseg = frame_states_[current_frame_index_].shmseg;

  current_frame_index_ = (current_frame_index_ + 1) % frame_states_.size();
}

void XShmImagePool::DispatchShmCompletionEvent(
    x11::Shm::CompletionEvent event) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(event.offset, 0UL);
  DCHECK(enable_multibuffering_);

  for (auto it = swap_closures_.begin(); it != swap_closures_.end(); ++it) {
    if (event.shmseg == it->shmseg) {
      std::move(it->closure).Run();
      swap_closures_.erase(it);
      return;
    }
  }
}

void XShmImagePool::OnEvent(const x11::Event& xev) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(enable_multibuffering_);

  auto* completion = xev.As<x11::Shm::CompletionEvent>();
  if (completion && completion->drawable.value == drawable_.value)
    DispatchShmCompletionEvent(*completion);
}

void XShmImagePool::Cleanup() {
  for (FrameState& state : frame_states_) {
    if (state.shmaddr)
      shmdt(state.shmaddr.ExtractAsDangling());
    if (state.shmem_attached_to_server)
      connection_->shm().Detach({state.shmseg});
    state.shmem_attached_to_server = false;
    state.shmseg = x11::Shm::Seg{};
    state.shmid = 0;
    state.shmaddr = nullptr;
  }
  frame_bytes_ = 0;
  pixel_size_ = gfx::Size();
  current_frame_index_ = 0;
  ready_ = false;
}

}  // namespace ui