910e62b5创建于 1月15日历史提交
// 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_software_bitmap_presenter.h"

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <cstring>
#include <memory>
#include <utility>

#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted_memory.h"
#include "skia/ext/legacy_display_globals.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/gfx/native_ui_types.h"
#include "ui/gfx/x/atom_cache.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/xproto.h"
#include "ui/gfx/x/xproto_types.h"

namespace ui {

namespace {

constexpr int kMaxFramesPending = 2;

class ScopedPixmap {
 public:
  ScopedPixmap(x11::Connection* connection, x11::Pixmap pixmap)
      : connection_(connection), pixmap_(pixmap) {}

  ScopedPixmap(const ScopedPixmap&) = delete;
  ScopedPixmap& operator=(const ScopedPixmap&) = delete;

  ~ScopedPixmap() {
    if (pixmap_ != x11::Pixmap::None) {
      connection_->FreePixmap({pixmap_});
    }
  }

 private:
  const raw_ptr<x11::Connection> connection_;
  x11::Pixmap pixmap_;
};

}  // namespace

// static
bool X11SoftwareBitmapPresenter::CompositeBitmap(x11::Connection* connection,
                                                 x11::Drawable widget,
                                                 int x,
                                                 int y,
                                                 int width,
                                                 int height,
                                                 int depth,
                                                 x11::GraphicsContext gc,
                                                 const void* data) {
  int16_t x_i16 = x;
  int16_t y_i16 = y;
  uint16_t w_u16 = width;
  uint16_t h_u16 = height;
  uint8_t d_u8 = depth;
  connection->ClearArea({false, widget, x_i16, y_i16, w_u16, h_u16});

  constexpr auto kAllPlanes =
      std::numeric_limits<decltype(x11::GetImageRequest::plane_mask)>::max();

  scoped_refptr<x11::UnsizedRefCountedMemory> bg;
  auto req = connection->GetImage({x11::ImageFormat::ZPixmap, widget, x_i16,
                                   y_i16, w_u16, h_u16, kAllPlanes});
  if (auto reply = req.Sync()) {
    bg = reply->data;
  } else {
    auto pixmap_id = connection->GenerateId<x11::Pixmap>();
    connection->CreatePixmap({d_u8, pixmap_id, widget, w_u16, h_u16});
    ScopedPixmap pixmap(connection, pixmap_id);

    connection->ChangeGC(x11::ChangeGCRequest{
        .gc = gc, .subwindow_mode = x11::SubwindowMode::IncludeInferiors});
    connection->CopyArea(
        {widget, pixmap_id, gc, x_i16, y_i16, 0, 0, w_u16, h_u16});
    connection->ChangeGC(x11::ChangeGCRequest{
        .gc = gc, .subwindow_mode = x11::SubwindowMode::ClipByChildren});

    auto pix_req = connection->GetImage(
        {x11::ImageFormat::ZPixmap, pixmap_id, 0, 0, w_u16, h_u16, kAllPlanes});
    auto pix_reply = pix_req.Sync();
    if (!pix_reply) {
      return false;
    }
    bg = pix_reply->data;
  }

  SkBitmap bg_bitmap;
  SkImageInfo image_info = SkImageInfo::Make(
      w_u16, h_u16, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
  if (!bg_bitmap.installPixels(image_info, bg->bytes(),
                               image_info.minRowBytes())) {
    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.drawImage(fg_bitmap.asImage(), 0, 0);

  connection->PutImage({x11::ImageFormat::ZPixmap, widget, gc, w_u16, h_u16,
                        x_i16, y_i16, 0, d_u8,
                        UNSAFE_TODO(x11::SizedRefCountedMemory::From(
                            bg, size_t{w_u16} * h_u16))});

  return true;
}

X11SoftwareBitmapPresenter::X11SoftwareBitmapPresenter(
    x11::Connection& connection,
    gfx::AcceleratedWidget widget,
    bool enable_multibuffering)
    : widget_(static_cast<x11::Window>(widget)),
      connection_(connection),
      enable_multibuffering_(enable_multibuffering) {
  DCHECK_NE(widget_, x11::Window::None);

  gc_ = connection_->GenerateId<x11::GraphicsContext>();
  connection_->CreateGC({gc_, widget_});

  if (auto response = connection_->GetWindowAttributes({widget_}).Sync()) {
    visual_ = response->visual;
    depth_ = connection_->GetVisualInfoFromId(visual_)->format->depth;
  } else {
    LOG(ERROR) << "XGetWindowAttributes failed for window "
               << static_cast<uint32_t>(widget_);
    return;
  }

  shm_pool_ = std::make_unique<ui::XShmImagePool>(
      &connection_.get(), widget_, visual_, depth_, MaxFramesPending(),
      enable_multibuffering_);

  // TODO(thomasanderson): Avoid going through the X11 server to plumb this
  // property in.
  connection_->GetPropertyAs(widget_, x11::GetAtom("CHROMIUM_COMPOSITE_WINDOW"),
                             &composite_);
}

X11SoftwareBitmapPresenter::~X11SoftwareBitmapPresenter() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (gc_ != x11::GraphicsContext{}) {
    connection_->FreeGC({gc_});
  }
}

bool X11SoftwareBitmapPresenter::ShmPoolReady() const {
  return shm_pool_ && shm_pool_->Ready();
}

void X11SoftwareBitmapPresenter::Resize(const gfx::Size& pixel_size) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  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(visual_);
    if (color_type == kUnknown_SkColorType) {
      return;
    }
    SkImageInfo info = SkImageInfo::Make(viewport_pixel_size_.width(),
                                         viewport_pixel_size_.height(),
                                         color_type, kOpaque_SkAlphaType);
    SkSurfaceProps props = skia::LegacyDisplayGlobals::GetSkSurfaceProps();
    surface_ = SkSurfaces::Raster(info, &props);
  }
}

SkCanvas* X11SoftwareBitmapPresenter::GetSkCanvas() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (ShmPoolReady()) {
    return shm_pool_->CurrentCanvas();
  } else if (surface_) {
    return surface_->getCanvas();
  }
  return nullptr;
}

void X11SoftwareBitmapPresenter::EndPaint(const gfx::Rect& damage_rect) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  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.
    x11::Shm::PutImageRequest put_image_request{
        .drawable = widget_,
        .gc = gc_,
        .total_width =
            static_cast<uint16_t>(shm_pool_->CurrentBitmap().width()),
        .total_height =
            static_cast<uint16_t>(shm_pool_->CurrentBitmap().height()),
        .src_x = static_cast<uint16_t>(rect.x()),
        .src_y = static_cast<uint16_t>(rect.y()),
        .src_width = static_cast<uint16_t>(rect.width()),
        .src_height = static_cast<uint16_t>(rect.height()),
        .dst_x = static_cast<int16_t>(rect.x()),
        .dst_y = static_cast<int16_t>(rect.y()),
        .depth = static_cast<uint8_t>(depth_),
        .format = x11::ImageFormat::ZPixmap,
        .send_event = enable_multibuffering_,
        .shmseg = shm_pool_->CurrentSegment(),
        .offset = 0,
    };
    connection_->shm().PutImage(put_image_request);
    needs_swap_ = true;
    // Flush now to ensure the X server gets the request as early as
    // possible to reduce frame-to-frame latency.
    connection_->Flush();
    return;
  }
  if (surface_) {
    surface_->peekPixels(&skia_pixmap);
  }

  if (!skia_pixmap.addr()) {
    return;
  }

  if (composite_ && CompositeBitmap(&connection_.get(), widget_, rect.x(),
                                    rect.y(), rect.width(), rect.height(),
                                    depth_, gc_, skia_pixmap.addr())) {
    // Flush now to ensure the X server gets the request as early as
    // possible to reduce frame-to-frame latency.

    connection_->Flush();
    return;
  }

  auto* connection = x11::Connection::Get();
  DrawPixmap(connection, visual_, widget_, gc_, skia_pixmap, rect.x(), rect.y(),
             rect.x(), rect.y(), rect.width(), rect.height());

  // Flush now to ensure the X server gets the request as early as
  // possible to reduce frame-to-frame latency.
  connection_->Flush();
}

void X11SoftwareBitmapPresenter::OnSwapBuffers(
    SwapBuffersCallback swap_ack_callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (enable_multibuffering_ && ShmPoolReady() && needs_swap_) {
    shm_pool_->SwapBuffers(std::move(swap_ack_callback));
  } else {
    std::move(swap_ack_callback).Run(viewport_pixel_size_);
  }
  needs_swap_ = false;
}

int X11SoftwareBitmapPresenter::MaxFramesPending() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return enable_multibuffering_ ? kMaxFramesPending : 1;
}

}  // namespace ui