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