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_software_bitmap_presenter.h"
6 
7 #include <stddef.h>
8 #include <stdint.h>
9 #include <string.h>
10 
11 #include <cstring>
12 #include <memory>
13 #include <utility>
14 
15 #include "base/bind.h"
16 #include "base/logging.h"
17 #include "base/macros.h"
18 #include "base/memory/ref_counted_memory.h"
19 #include "skia/ext/legacy_display_globals.h"
20 #include "third_party/skia/include/core/SkCanvas.h"
21 #include "third_party/skia/include/core/SkImageInfo.h"
22 #include "third_party/skia/include/core/SkSurface.h"
23 #include "ui/base/x/x11_shm_image_pool.h"
24 #include "ui/base/x/x11_util.h"
25 #include "ui/gfx/native_widget_types.h"
26 #include "ui/gfx/x/connection.h"
27 #include "ui/gfx/x/xproto.h"
28 #include "ui/gfx/x/xproto_types.h"
29 
30 namespace ui {
31 
32 namespace {
33 
34 constexpr int kMaxFramesPending = 2;
35 
36 class ScopedPixmap {
37  public:
ScopedPixmap(x11::Connection * connection,x11::Pixmap pixmap)38   ScopedPixmap(x11::Connection* connection, x11::Pixmap pixmap)
39       : connection_(connection), pixmap_(pixmap) {}
40 
~ScopedPixmap()41   ~ScopedPixmap() {
42     if (pixmap_ != x11::Pixmap::None)
43       connection_->FreePixmap({pixmap_});
44   }
45 
46  private:
47   x11::Connection* const connection_;
48   x11::Pixmap pixmap_;
49   DISALLOW_COPY_AND_ASSIGN(ScopedPixmap);
50 };
51 
52 }  // namespace
53 
54 // static
CompositeBitmap(x11::Connection * connection,x11::Drawable widget,int x,int y,int width,int height,int depth,x11::GraphicsContext gc,const void * data)55 bool X11SoftwareBitmapPresenter::CompositeBitmap(x11::Connection* connection,
56                                                  x11::Drawable widget,
57                                                  int x,
58                                                  int y,
59                                                  int width,
60                                                  int height,
61                                                  int depth,
62                                                  x11::GraphicsContext gc,
63                                                  const void* data) {
64   connection->ClearArea({false, widget, x, y, width, height});
65 
66   constexpr auto kAllPlanes =
67       std::numeric_limits<decltype(x11::GetImageRequest::plane_mask)>::max();
68 
69   scoped_refptr<base::RefCountedMemory> bg;
70   auto req = connection->GetImage(
71       {x11::ImageFormat::ZPixmap, widget, x, y, width, height, kAllPlanes});
72   if (auto reply = req.Sync()) {
73     bg = reply->data;
74   } else {
75     auto pixmap_id = connection->GenerateId<x11::Pixmap>();
76     connection->CreatePixmap({depth, pixmap_id, widget, width, height});
77     ScopedPixmap pixmap(connection, pixmap_id);
78 
79     connection->ChangeGC(x11::ChangeGCRequest{
80         .gc = gc, .subwindow_mode = x11::SubwindowMode::IncludeInferiors});
81     connection->CopyArea({widget, pixmap_id, gc, x, y, 0, 0, width, height});
82     connection->ChangeGC(x11::ChangeGCRequest{
83         .gc = gc, .subwindow_mode = x11::SubwindowMode::ClipByChildren});
84 
85     auto req = connection->GetImage({x11::ImageFormat::ZPixmap, pixmap_id, 0, 0,
86                                      width, height, kAllPlanes});
87     if (auto reply = req.Sync())
88       bg = reply->data;
89     else
90       return false;
91   }
92 
93   SkBitmap bg_bitmap;
94   SkImageInfo image_info = SkImageInfo::Make(
95       width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
96   if (!bg_bitmap.installPixels(image_info, const_cast<uint8_t*>(bg->data()),
97                                image_info.minRowBytes())) {
98     return false;
99   }
100   SkCanvas canvas(bg_bitmap);
101 
102   SkBitmap fg_bitmap;
103   image_info = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType,
104                                  kPremul_SkAlphaType);
105   if (!fg_bitmap.installPixels(image_info, const_cast<void*>(data), 4 * width))
106     return false;
107   canvas.drawBitmap(fg_bitmap, 0, 0);
108   canvas.flush();
109 
110   connection->PutImage({x11::ImageFormat::ZPixmap, widget, gc, width, height, x,
111                         y, 0, depth, bg});
112 
113   return true;
114 }
115 
X11SoftwareBitmapPresenter(x11::Connection * connection,gfx::AcceleratedWidget widget,bool enable_multibuffering)116 X11SoftwareBitmapPresenter::X11SoftwareBitmapPresenter(
117     x11::Connection* connection,
118     gfx::AcceleratedWidget widget,
119     bool enable_multibuffering)
120     : widget_(static_cast<x11::Window>(widget)),
121       connection_(connection),
122       enable_multibuffering_(enable_multibuffering) {
123   DCHECK_NE(widget_, x11::Window::None);
124 
125   gc_ = connection_->GenerateId<x11::GraphicsContext>();
126   connection_->CreateGC({gc_, widget_});
127 
128   if (auto response = connection_->GetWindowAttributes({widget_}).Sync()) {
129     visual_ = response->visual;
130     depth_ = connection_->GetVisualInfoFromId(visual_)->format->depth;
131   } else {
132     LOG(ERROR) << "XGetWindowAttributes failed for window "
133                << static_cast<uint32_t>(widget_);
134     return;
135   }
136 
137   shm_pool_ = std::make_unique<ui::XShmImagePool>(connection_, widget_, visual_,
138                                                   depth_, MaxFramesPending(),
139                                                   enable_multibuffering_);
140 
141   // TODO(thomasanderson): Avoid going through the X11 server to plumb this
142   // property in.
143   ui::GetIntProperty(widget_, "CHROMIUM_COMPOSITE_WINDOW", &composite_);
144 }
145 
~X11SoftwareBitmapPresenter()146 X11SoftwareBitmapPresenter::~X11SoftwareBitmapPresenter() {
147   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
148   if (gc_ != x11::GraphicsContext{})
149     connection_->FreeGC({gc_});
150 }
151 
ShmPoolReady() const152 bool X11SoftwareBitmapPresenter::ShmPoolReady() const {
153   return shm_pool_ && shm_pool_->Ready();
154 }
155 
Resize(const gfx::Size & pixel_size)156 void X11SoftwareBitmapPresenter::Resize(const gfx::Size& pixel_size) {
157   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
158   if (pixel_size == viewport_pixel_size_)
159     return;
160   viewport_pixel_size_ = pixel_size;
161   // Fallback to the non-shm codepath when |composite_| is true, which only
162   // happens for status icon windows that are typically 16x16px.  It's possible
163   // to add a shm codepath, but it wouldn't be buying much since it would only
164   // affect windows that are tiny and infrequently updated.
165   if (!composite_ && shm_pool_ && shm_pool_->Resize(pixel_size)) {
166     needs_swap_ = false;
167     surface_ = nullptr;
168   } else {
169     SkColorType color_type = ColorTypeForVisual(visual_);
170     if (color_type == kUnknown_SkColorType)
171       return;
172     SkImageInfo info = SkImageInfo::Make(viewport_pixel_size_.width(),
173                                          viewport_pixel_size_.height(),
174                                          color_type, kOpaque_SkAlphaType);
175     SkSurfaceProps props = skia::LegacyDisplayGlobals::GetSkSurfaceProps();
176     surface_ = SkSurface::MakeRaster(info, &props);
177   }
178 }
179 
GetSkCanvas()180 SkCanvas* X11SoftwareBitmapPresenter::GetSkCanvas() {
181   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
182   if (ShmPoolReady())
183     return shm_pool_->CurrentCanvas();
184   else if (surface_)
185     return surface_->getCanvas();
186   return nullptr;
187 }
188 
EndPaint(const gfx::Rect & damage_rect)189 void X11SoftwareBitmapPresenter::EndPaint(const gfx::Rect& damage_rect) {
190   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
191   gfx::Rect rect = damage_rect;
192   rect.Intersect(gfx::Rect(viewport_pixel_size_));
193   if (rect.IsEmpty())
194     return;
195 
196   SkPixmap skia_pixmap;
197 
198   if (ShmPoolReady()) {
199     // TODO(thomasanderson): Investigate direct rendering with DRI3 to avoid any
200     // unnecessary X11 IPC or buffer copying.
201     x11::Shm::PutImageRequest put_image_request{
202         .drawable = widget_,
203         .gc = gc_,
204         .total_width = shm_pool_->CurrentBitmap().width(),
205         .total_height = shm_pool_->CurrentBitmap().height(),
206         .src_x = rect.x(),
207         .src_y = rect.y(),
208         .src_width = rect.width(),
209         .src_height = rect.height(),
210         .dst_x = rect.x(),
211         .dst_y = rect.y(),
212         .depth = depth_,
213         .format = x11::ImageFormat::ZPixmap,
214         .send_event = enable_multibuffering_,
215         .shmseg = shm_pool_->CurrentSegment(),
216         .offset = 0,
217     };
218     connection_->shm().PutImage(put_image_request);
219     needs_swap_ = true;
220     // Flush now to ensure the X server gets the request as early as
221     // possible to reduce frame-to-frame latency.
222     connection_->Flush();
223     return;
224   }
225   if (surface_)
226     surface_->peekPixels(&skia_pixmap);
227 
228   if (!skia_pixmap.addr())
229     return;
230 
231   if (composite_ &&
232       CompositeBitmap(connection_, widget_, rect.x(), rect.y(), rect.width(),
233                       rect.height(), depth_, gc_, skia_pixmap.addr())) {
234     // Flush now to ensure the X server gets the request as early as
235     // possible to reduce frame-to-frame latency.
236 
237     connection_->Flush();
238     return;
239   }
240 
241   auto* connection = x11::Connection::Get();
242   DrawPixmap(connection, visual_, widget_, gc_, skia_pixmap, rect.x(), rect.y(),
243              rect.x(), rect.y(), rect.width(), rect.height());
244 
245   // Flush now to ensure the X server gets the request as early as
246   // possible to reduce frame-to-frame latency.
247   connection_->Flush();
248 }
249 
OnSwapBuffers(SwapBuffersCallback swap_ack_callback)250 void X11SoftwareBitmapPresenter::OnSwapBuffers(
251     SwapBuffersCallback swap_ack_callback) {
252   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
253   if (enable_multibuffering_ && ShmPoolReady() && needs_swap_)
254     shm_pool_->SwapBuffers(std::move(swap_ack_callback));
255   else
256     std::move(swap_ack_callback).Run(viewport_pixel_size_);
257   needs_swap_ = false;
258 }
259 
MaxFramesPending() const260 int X11SoftwareBitmapPresenter::MaxFramesPending() const {
261   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
262   return enable_multibuffering_ ? kMaxFramesPending : 1;
263 }
264 
265 }  // namespace ui
266