blob: 6d11f2ce4a8c58d1b6866202f15a59bb4e656a91 [file] [log] [blame]
// Copyright 2018 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/ozone/common/linux/gbm_wrapper.h"
#include <gbm.h>
#include <memory>
#include <utility>
#include "base/posix/eintr_wrapper.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/ozone/common/linux/drm_util_linux.h"
#include "ui/ozone/common/linux/gbm_buffer.h"
#include "ui/ozone/common/linux/gbm_device.h"
#if !defined(MINIGBM)
#include <fcntl.h>
#include <xf86drm.h>
#endif
namespace gbm_wrapper {
namespace {
int GetPlaneFdForBo(gbm_bo* bo, size_t plane) {
#if defined(MINIGBM)
return gbm_bo_get_plane_fd(bo, plane);
#else
const int plane_count = gbm_bo_get_plane_count(bo);
DCHECK(plane_count > 0 && plane < static_cast<size_t>(plane_count));
// System linux gbm (or Mesa gbm) does not provide fds per plane basis. Thus,
// get plane handle and use drm ioctl to get a prime fd out of it avoid having
// two different branches for minigbm and Mesa gbm here.
gbm_device* gbm_dev = gbm_bo_get_device(bo);
int dev_fd = gbm_device_get_fd(gbm_dev);
DCHECK_GE(dev_fd, 0);
const uint32_t plane_handle = gbm_bo_get_handle_for_plane(bo, plane).u32;
int fd = -1;
int ret;
// Use DRM_RDWR to allow the fd to be mappable in another process.
ret = drmPrimeHandleToFD(dev_fd, plane_handle, DRM_CLOEXEC | DRM_RDWR, &fd);
// Older DRM implementations blocked DRM_RDWR, but gave a read/write mapping
// anyways
if (ret)
ret = drmPrimeHandleToFD(dev_fd, plane_handle, DRM_CLOEXEC, &fd);
return ret ? ret : fd;
#endif
}
size_t GetSizeOfPlane(gbm_bo* bo,
uint32_t format,
const gfx::Size& size,
size_t plane) {
#if defined(MINIGBM)
return gbm_bo_get_plane_size(bo, plane);
#else
DCHECK(!size.IsEmpty());
// Get row size of the plane, stride and subsampled height to finally get the
// size of a plane in bytes.
const gfx::BufferFormat buffer_format =
ui::GetBufferFormatFromFourCCFormat(format);
const base::CheckedNumeric<size_t> stride_for_plane =
gbm_bo_get_stride_for_plane(bo, plane);
const base::CheckedNumeric<size_t> subsampled_height =
size.height() /
gfx::SubsamplingFactorForBufferFormat(buffer_format, plane);
// Apply subsampling factor to get size in bytes.
const base::CheckedNumeric<size_t> checked_plane_size =
subsampled_height * stride_for_plane;
return checked_plane_size.ValueOrDie();
#endif
}
} // namespace
class Buffer final : public ui::GbmBuffer {
public:
Buffer(struct gbm_bo* bo,
uint32_t format,
uint32_t flags,
uint64_t modifier,
const gfx::Size& size,
gfx::NativePixmapHandle handle)
: bo_(bo),
format_(format),
format_modifier_(modifier),
flags_(flags),
size_(size),
handle_(std::move(handle)) {}
~Buffer() override {
DCHECK(!mmap_data_);
gbm_bo_destroy(bo_);
}
uint32_t GetFormat() const override { return format_; }
uint64_t GetFormatModifier() const override { return format_modifier_; }
uint32_t GetFlags() const override { return flags_; }
// TODO(reveman): This should not be needed once crbug.com/597932 is fixed,
// as the size would be queried directly from the underlying bo.
gfx::Size GetSize() const override { return size_; }
gfx::BufferFormat GetBufferFormat() const override {
return ui::GetBufferFormatFromFourCCFormat(format_);
}
bool AreFdsValid() const override {
if (handle_.planes.empty())
return false;
for (const auto& plane : handle_.planes) {
if (!plane.fd.is_valid())
return false;
}
return true;
}
size_t GetNumPlanes() const override { return handle_.planes.size(); }
int GetPlaneFd(size_t plane) const override {
DCHECK_LT(plane, handle_.planes.size());
return handle_.planes[plane].fd.get();
}
uint32_t GetPlaneStride(size_t plane) const override {
DCHECK_LT(plane, handle_.planes.size());
return handle_.planes[plane].stride;
}
size_t GetPlaneOffset(size_t plane) const override {
DCHECK_LT(plane, handle_.planes.size());
return handle_.planes[plane].offset;
}
size_t GetPlaneSize(size_t plane) const override {
DCHECK_LT(plane, handle_.planes.size());
return static_cast<size_t>(handle_.planes[plane].size);
}
uint32_t GetPlaneHandle(size_t plane) const override {
DCHECK_LT(plane, handle_.planes.size());
return gbm_bo_get_handle_for_plane(bo_, plane).u32;
}
uint32_t GetHandle() const override { return gbm_bo_get_handle(bo_).u32; }
gfx::NativePixmapHandle ExportHandle() const override {
return CloneHandleForIPC(handle_);
}
sk_sp<SkSurface> GetSurface() override {
DCHECK(!mmap_data_);
uint32_t stride;
void* addr;
addr =
#if defined(MINIGBM)
gbm_bo_map(bo_, 0, 0, gbm_bo_get_width(bo_), gbm_bo_get_height(bo_),
GBM_BO_TRANSFER_READ_WRITE, &stride, &mmap_data_, 0);
#else
gbm_bo_map(bo_, 0, 0, gbm_bo_get_width(bo_), gbm_bo_get_height(bo_),
GBM_BO_TRANSFER_READ_WRITE, &stride, &mmap_data_);
#endif
if (!addr)
return nullptr;
SkImageInfo info =
SkImageInfo::MakeN32Premul(size_.width(), size_.height());
return SkSurface::MakeRasterDirectReleaseProc(info, addr, stride,
&Buffer::UnmapGbmBo, this);
}
private:
static void UnmapGbmBo(void* pixels, void* context) {
Buffer* buffer = static_cast<Buffer*>(context);
gbm_bo_unmap(buffer->bo_, buffer->mmap_data_);
buffer->mmap_data_ = nullptr;
}
gbm_bo* const bo_;
void* mmap_data_ = nullptr;
const uint32_t format_;
const uint64_t format_modifier_;
const uint32_t flags_;
const gfx::Size size_;
const gfx::NativePixmapHandle handle_;
DISALLOW_COPY_AND_ASSIGN(Buffer);
};
std::unique_ptr<Buffer> CreateBufferForBO(struct gbm_bo* bo,
uint32_t format,
const gfx::Size& size,
uint32_t flags) {
DCHECK(bo);
gfx::NativePixmapHandle handle;
const uint64_t modifier = gbm_bo_get_modifier(bo);
const int plane_count = gbm_bo_get_plane_count(bo);
// The Mesa's gbm implementation explicitly checks whether plane count <= and
// returns 1 if the condition is true. Nevertheless, use a DCHECK here to make
// sure the condition is not broken there.
DCHECK_GT(plane_count, 0);
// Ensure there are no differences in integer signs by casting any possible
// values to size_t.
for (size_t i = 0; i < static_cast<size_t>(plane_count); ++i) {
// The fd returned by gbm_bo_get_fd is not ref-counted and need to be
// kept open for the lifetime of the buffer.
base::ScopedFD fd(GetPlaneFdForBo(bo, i));
if (!fd.is_valid()) {
PLOG(ERROR) << "Failed to export buffer to dma_buf";
gbm_bo_destroy(bo);
return nullptr;
}
handle.planes.emplace_back(
gbm_bo_get_stride_for_plane(bo, i), gbm_bo_get_offset(bo, i),
GetSizeOfPlane(bo, format, size, i), std::move(fd));
}
handle.modifier = modifier;
return std::make_unique<Buffer>(bo, format, flags, modifier, size,
std::move(handle));
}
class Device final : public ui::GbmDevice {
public:
Device(gbm_device* device) : device_(device) {}
~Device() override { gbm_device_destroy(device_); }
std::unique_ptr<ui::GbmBuffer> CreateBuffer(uint32_t format,
const gfx::Size& size,
uint32_t flags) override {
struct gbm_bo* bo =
gbm_bo_create(device_, size.width(), size.height(), format, flags);
if (!bo)
return nullptr;
return CreateBufferForBO(bo, format, size, flags);
}
std::unique_ptr<ui::GbmBuffer> CreateBufferWithModifiers(
uint32_t format,
const gfx::Size& size,
uint32_t flags,
const std::vector<uint64_t>& modifiers) override {
if (modifiers.empty())
return CreateBuffer(format, size, flags);
struct gbm_bo* bo = gbm_bo_create_with_modifiers(
device_, size.width(), size.height(), format, modifiers.data(),
modifiers.size());
if (!bo)
return nullptr;
return CreateBufferForBO(bo, format, size, flags);
}
std::unique_ptr<ui::GbmBuffer> CreateBufferFromHandle(
uint32_t format,
const gfx::Size& size,
gfx::NativePixmapHandle handle) override {
DCHECK_EQ(handle.planes[0].offset, 0u);
// Try to use scanout if supported.
int gbm_flags = GBM_BO_USE_SCANOUT;
#if defined(MINIGBM)
gbm_flags |= GBM_BO_USE_TEXTURING;
#endif
if (!gbm_device_is_format_supported(device_, format, gbm_flags))
gbm_flags &= ~GBM_BO_USE_SCANOUT;
struct gbm_bo* bo = nullptr;
if (!gbm_device_is_format_supported(device_, format, gbm_flags)) {
LOG(ERROR) << "gbm format not supported: " << format;
return nullptr;
}
struct gbm_import_fd_modifier_data fd_data;
fd_data.width = size.width();
fd_data.height = size.height();
fd_data.format = format;
fd_data.num_fds = handle.planes.size();
fd_data.modifier = handle.modifier;
DCHECK_LE(handle.planes.size(), 3u);
for (size_t i = 0; i < handle.planes.size(); ++i) {
fd_data.fds[i] = handle.planes[i < handle.planes.size() ? i : 0].fd.get();
fd_data.strides[i] = handle.planes[i].stride;
fd_data.offsets[i] = handle.planes[i].offset;
}
// The fd passed to gbm_bo_import is not ref-counted and need to be
// kept open for the lifetime of the buffer.
bo = gbm_bo_import(device_, GBM_BO_IMPORT_FD_MODIFIER, &fd_data, gbm_flags);
if (!bo) {
LOG(ERROR) << "nullptr returned from gbm_bo_import";
return nullptr;
}
return std::make_unique<Buffer>(bo, format, gbm_flags, handle.modifier,
size, std::move(handle));
}
private:
gbm_device* const device_;
DISALLOW_COPY_AND_ASSIGN(Device);
};
} // namespace gbm_wrapper
namespace ui {
std::unique_ptr<GbmDevice> CreateGbmDevice(int fd) {
gbm_device* device = gbm_create_device(fd);
if (!device)
return nullptr;
return std::make_unique<gbm_wrapper::Device>(device);
}
} // namespace ui