blob: f72b5b3ac6b73e1b5ce06a94c23afe36ca0653ac [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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_software_bitmap_presenter.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <cstring>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/macros.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "ui/base/x/x11_shm_image_pool.h"
#include "ui/base/x/x11_util.h"
#include "ui/base/x/x11_util_internal.h"
#include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_error_tracker.h"
#include "ui/gfx/x/x11_types.h"
namespace ui {
namespace {
constexpr int kMaxFramesPending = 2;
class ScopedPixmap {
public:
ScopedPixmap(XDisplay* display, Pixmap pixmap)
: display_(display), pixmap_(pixmap) {}
~ScopedPixmap() {
if (pixmap_)
XFreePixmap(display_, pixmap_);
}
operator Pixmap() const { return pixmap_; }
private:
XDisplay* display_;
Pixmap pixmap_;
DISALLOW_COPY_AND_ASSIGN(ScopedPixmap);
};
} // namespace
// static
bool X11SoftwareBitmapPresenter::CompositeBitmap(XDisplay* display,
XID widget,
int x,
int y,
int width,
int height,
int depth,
GC gc,
const void* data) {
XClearArea(display, widget, x, y, width, height, false);
ui::XScopedImage bg;
{
gfx::X11ErrorTracker ignore_x_errors;
bg.reset(
XGetImage(display, widget, x, y, width, height, AllPlanes, ZPixmap));
}
// XGetImage() may fail if the drawable is a window and the window is not
// fully in the bounds of its parent.
if (!bg) {
ScopedPixmap pixmap(display,
XCreatePixmap(display, widget, width, height, depth));
if (!pixmap)
return false;
XGCValues gcv;
gcv.subwindow_mode = IncludeInferiors;
XChangeGC(display, gc, GCSubwindowMode, &gcv);
XCopyArea(display, widget, pixmap, gc, x, y, width, height, 0, 0);
gcv.subwindow_mode = ClipByChildren;
XChangeGC(display, gc, GCSubwindowMode, &gcv);
bg.reset(
XGetImage(display, pixmap, 0, 0, width, height, AllPlanes, ZPixmap));
}
if (!bg)
return false;
SkBitmap bg_bitmap;
SkImageInfo image_info =
SkImageInfo::Make(bg->width, bg->height,
bg->byte_order == LSBFirst ? kBGRA_8888_SkColorType
: kRGBA_8888_SkColorType,
kPremul_SkAlphaType);
if (!bg_bitmap.installPixels(image_info, bg->data, bg->bytes_per_line))
return false;
SkCanvas canvas(bg_bitmap);
SkBitmap fg_bitmap;
image_info = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType,
kPremul_SkAlphaType);
if (!fg_bitmap.installPixels(image_info, const_cast<void*>(data), 4 * width))
return false;
canvas.drawBitmap(fg_bitmap, 0, 0);
canvas.flush();
XPutImage(display, widget, gc, bg.get(), x, y, x, y, width, height);
return true;
}
X11SoftwareBitmapPresenter::X11SoftwareBitmapPresenter(
gfx::AcceleratedWidget widget,
scoped_refptr<base::SequencedTaskRunner> host_task_runner,
scoped_refptr<base::SequencedTaskRunner> event_task_runner)
: widget_(widget),
display_(gfx::GetXDisplay()),
gc_(nullptr),
host_task_runner_(host_task_runner),
event_task_runner_(event_task_runner) {
DCHECK_NE(widget_, gfx::kNullAcceleratedWidget);
gc_ = XCreateGC(display_, widget_, 0, nullptr);
memset(&attributes_, 0, sizeof(attributes_));
if (!XGetWindowAttributes(display_, widget_, &attributes_)) {
LOG(ERROR) << "XGetWindowAttributes failed for window " << widget_;
return;
}
shm_pool_ = base::MakeRefCounted<ui::XShmImagePool>(
host_task_runner, event_task_runner, display_, widget_,
attributes_.visual, attributes_.depth, kMaxFramesPending);
shm_pool_->Initialize();
// TODO(thomasanderson): Avoid going through the X11 server to plumb this
// property in.
ui::GetIntProperty(widget_, "CHROMIUM_COMPOSITE_WINDOW", &composite_);
}
X11SoftwareBitmapPresenter::~X11SoftwareBitmapPresenter() {
if (gc_)
XFreeGC(display_, gc_);
if (shm_pool_)
shm_pool_->Teardown();
}
bool X11SoftwareBitmapPresenter::ShmPoolReady() const {
return shm_pool_ && shm_pool_->Ready();
}
void X11SoftwareBitmapPresenter::FlushAfterPutImage() {
// Ensure the new window content appears immediately. On a TYPE_UI thread we
// can rely on the message loop to flush for us so XFlush() isn't necessary.
// However, this code can run on a different thread and would have to wait for
// the TYPE_UI thread to no longer be idle before a flush happens.
XFlush(display_);
// Work around a race condition caused by XFlush above. Explanation: XFlush()
// flushes all requests and *also* reads events. It's unclear why it does
// this, but there's no alternative Xlib function that flushes the requests
// and *doesn't* read any events, so this workaround is necessary. In
// |event_task_runner_|'s message loop, poll() is called on the underlying
// XDisplay's fd to dispatch toplevel events. When the fd is readable, poll()
// exits and we (via Xlib) check for new events by read()ing from the fd. But
// if the event dispatcher is currently dispatching an event, then our call to
// XFlush() may read events into the event queue which will make the fd
// blocking since there's no more data to read, so poll() won't wake up until
// a new event comes, which may take a long time. Forcing the event loop to
// wake up with a dummy event fixes the race condition.
if (event_task_runner_)
event_task_runner_->PostTask(FROM_HERE, base::BindOnce([] {}));
}
void X11SoftwareBitmapPresenter::Resize(const gfx::Size& pixel_size) {
if (pixel_size == viewport_pixel_size_)
return;
viewport_pixel_size_ = pixel_size;
// Fallback to the non-shm codepath when |composite_| is true, which only
// happens for status icon windows that are typically 16x16px. It's possible
// to add a shm codepath, but it wouldn't be buying much since it would only
// affect windows that are tiny and infrequently updated.
if (!composite_ && shm_pool_ && shm_pool_->Resize(pixel_size)) {
needs_swap_ = false;
surface_ = nullptr;
} else {
SkColorType color_type = ColorTypeForVisual(attributes_.visual);
if (color_type == kUnknown_SkColorType)
return;
SkImageInfo info = SkImageInfo::Make(viewport_pixel_size_.width(),
viewport_pixel_size_.height(),
color_type, kOpaque_SkAlphaType);
surface_ = SkSurface::MakeRaster(info);
}
}
SkCanvas* X11SoftwareBitmapPresenter::GetSkCanvas() {
if (ShmPoolReady())
return shm_pool_->CurrentCanvas();
else if (surface_)
return surface_->getCanvas();
return nullptr;
}
void X11SoftwareBitmapPresenter::EndPaint(const gfx::Rect& damage_rect) {
gfx::Rect rect = damage_rect;
rect.Intersect(gfx::Rect(viewport_pixel_size_));
if (rect.IsEmpty())
return;
SkPixmap skia_pixmap;
if (ShmPoolReady()) {
// TODO(thomasanderson): Investigate direct rendering with DRI3 to avoid any
// unnecessary X11 IPC or buffer copying.
if (XShmPutImage(display_, widget_, gc_, shm_pool_->CurrentImage(),
rect.x(), rect.y(), rect.x(), rect.y(), rect.width(),
rect.height(), x11::True)) {
needs_swap_ = true;
FlushAfterPutImage();
return;
}
skia_pixmap = shm_pool_->CurrentBitmap().pixmap();
} else if (surface_) {
surface_->peekPixels(&skia_pixmap);
}
if (composite_ &&
CompositeBitmap(display_, widget_, rect.x(), rect.y(), rect.width(),
rect.height(), attributes_.depth, gc_,
skia_pixmap.addr())) {
FlushAfterPutImage();
return;
}
XImage image = {};
image.width = viewport_pixel_size_.width();
image.height = viewport_pixel_size_.height();
image.format = ZPixmap;
image.byte_order = LSBFirst;
image.bitmap_unit = 8;
image.bitmap_bit_order = LSBFirst;
image.depth = attributes_.depth;
image.bits_per_pixel = attributes_.visual->bits_per_rgb;
image.bits_per_pixel = skia_pixmap.info().bytesPerPixel() * 8;
image.bytes_per_line = skia_pixmap.rowBytes();
image.red_mask = attributes_.visual->red_mask;
image.green_mask = attributes_.visual->green_mask;
image.blue_mask = attributes_.visual->blue_mask;
image.data = reinterpret_cast<char*>(const_cast<void*>(skia_pixmap.addr()));
XPutImage(display_, widget_, gc_, &image, rect.x(), rect.y(), rect.x(),
rect.y(), rect.width(), rect.height());
FlushAfterPutImage();
}
void X11SoftwareBitmapPresenter::OnSwapBuffers(
SwapBuffersCallback swap_ack_callback) {
if (ShmPoolReady()) {
if (needs_swap_)
shm_pool_->SwapBuffers(std::move(swap_ack_callback));
else
std::move(swap_ack_callback).Run(viewport_pixel_size_);
needs_swap_ = false;
} else {
host_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(swap_ack_callback), viewport_pixel_size_));
}
}
int X11SoftwareBitmapPresenter::MaxFramesPending() const {
return kMaxFramesPending;
}
} // namespace ui