x11_shm_image_pool.cc 9.5 KB

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