blob: ccddf60eb6ea04f300d74a118effe9ebf5c37bf6 [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 "media/gpu/chromeos/platform_video_frame_utils.h"
#include <drm_fourcc.h>
#include <xf86drm.h>
#include <limits>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/containers/fixed_flat_set.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
#include "base/no_destructor.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "gpu/ipc/common/gpu_client_ids.h"
#include "gpu/ipc/common/gpu_memory_buffer_support.h"
#include "gpu/ipc/service/gpu_memory_buffer_factory.h"
#include "media/base/color_plane_layout.h"
#include "media/base/format_utils.h"
#include "media/base/scopedfd_helper.h"
#include "media/base/video_frame_layout.h"
#include "media/base/video_util.h"
#include "media/gpu/buffer_validation.h"
#include "media/gpu/macros.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/gpu_memory_buffer.h"
#include "ui/gfx/linux/drm_util_linux.h"
#include "ui/gfx/linux/gbm_buffer.h"
#include "ui/gfx/linux/gbm_device.h"
#include "ui/gfx/linux/gbm_util.h"
#include "ui/gfx/linux/gbm_wrapper.h"
#include "ui/gfx/linux/native_pixmap_dmabuf.h"
#include "ui/gfx/native_pixmap.h"
namespace media {
namespace {
// GbmDeviceWrapper is a singleton that provides thread-safe access to a
// ui::GbmDevice for the purposes of creating native BOs. The ui::GbmDevice is
// initialized with the first non-vgem render node found that works starting at
// /dev/dri/renderD128. Note that we have our own FD to the render node (i.e.,
// it's not shared with other components). Therefore, there should not be any
// concurrency issues if other components in the GPU process (like the VA-API
// driver) access the render node using their own FD.
class GbmDeviceWrapper {
public:
GbmDeviceWrapper(const GbmDeviceWrapper&) = delete;
GbmDeviceWrapper& operator=(const GbmDeviceWrapper&) = delete;
static GbmDeviceWrapper* Get() {
static base::NoDestructor<GbmDeviceWrapper> gbm_device_wrapper;
return gbm_device_wrapper.get();
}
// Creates a native BO and returns it as a GpuMemoryBufferHandle. Returns
// gfx::GpuMemoryBufferHandle() on failure.
gfx::GpuMemoryBufferHandle CreateGpuMemoryBuffer(
gfx::BufferFormat format,
const gfx::Size& size,
gfx::BufferUsage buffer_usage) {
base::AutoLock lock(lock_);
if (!gbm_device_)
return gfx::GpuMemoryBufferHandle();
const int fourcc_format = ui::GetFourCCFormatFromBufferFormat(format);
if (fourcc_format == DRM_FORMAT_INVALID)
return gfx::GpuMemoryBufferHandle();
std::unique_ptr<ui::GbmBuffer> buffer =
CreateGbmBuffer(fourcc_format, size, buffer_usage);
if (!buffer)
return gfx::GpuMemoryBufferHandle();
gfx::NativePixmapHandle native_pixmap_handle = buffer->ExportHandle();
if (native_pixmap_handle.planes.empty())
return gfx::GpuMemoryBufferHandle();
gfx::GpuMemoryBufferHandle gmb_handle;
gmb_handle.type = gfx::GpuMemoryBufferType::NATIVE_PIXMAP;
gmb_handle.id = GetNextGpuMemoryBufferId();
gmb_handle.native_pixmap_handle = std::move(native_pixmap_handle);
return gmb_handle;
}
std::unique_ptr<ui::GbmBuffer> ImportGpuMemoryBuffer(
gfx::BufferFormat format,
const gfx::Size& size,
gfx::NativePixmapHandle handle) {
base::AutoLock lock(lock_);
if (!gbm_device_)
return nullptr;
const int fourcc_format = ui::GetFourCCFormatFromBufferFormat(format);
if (fourcc_format == DRM_FORMAT_INVALID)
return nullptr;
return gbm_device_->CreateBufferFromHandle(fourcc_format, size,
std::move(handle));
}
private:
GbmDeviceWrapper() {
constexpr char kRenderNodeFilePattern[] = "/dev/dri/renderD%d";
// This loop ends on either the first card that does not exist or the first
// one that results in the creation of a gbm device.
for (int i = 128;; i++) {
base::FilePath dev_path(FILE_PATH_LITERAL(
base::StringPrintf(kRenderNodeFilePattern, i).c_str()));
render_node_file_ =
base::File(dev_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!render_node_file_.IsValid())
return;
// Skip the virtual graphics memory manager device.
drmVersionPtr version =
drmGetVersion(render_node_file_.GetPlatformFile());
if (!version)
continue;
std::string version_name(
version->name,
base::checked_cast<std::string::size_type>(version->name_len));
drmFreeVersion(version);
if (base::LowerCaseEqualsASCII(version_name, "vgem"))
continue;
gbm_device_ = ui::CreateGbmDevice(render_node_file_.GetPlatformFile());
if (gbm_device_)
return;
}
}
~GbmDeviceWrapper() = default;
std::unique_ptr<ui::GbmBuffer> CreateGbmBuffer(int fourcc_format,
const gfx::Size& size,
gfx::BufferUsage buffer_usage)
EXCLUSIVE_LOCKS_REQUIRED(lock_) {
uint32_t flags = ui::BufferUsageToGbmFlags(buffer_usage);
std::unique_ptr<ui::GbmBuffer> buffer =
gbm_device_->CreateBuffer(fourcc_format, size, flags);
if (buffer)
return buffer;
// For certain use cases, allocated buffers must be able to be set via kms
// on a CRTC. For those cases, the GBM_BO_USE_SCANOUT flag is required.
// For other use cases, GBM_BO_USE_SCANOUT may be preferred but is
// ultimately optional, so we can fall back to allocation without that
// flag.
constexpr auto kScanoutUsages = base::MakeFixedFlatSet<gfx::BufferUsage>(
{gfx::BufferUsage::SCANOUT,
gfx::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE,
gfx::BufferUsage::SCANOUT_FRONT_RENDERING});
if (!kScanoutUsages.contains(buffer_usage))
flags &= ~GBM_BO_USE_SCANOUT;
return gbm_device_->CreateBuffer(fourcc_format, size, flags);
}
friend class base::NoDestructor<GbmDeviceWrapper>;
base::Lock lock_;
base::File render_node_file_ GUARDED_BY(lock_);
std::unique_ptr<ui::GbmDevice> gbm_device_ GUARDED_BY(lock_);
};
gfx::GpuMemoryBufferHandle AllocateGpuMemoryBufferHandle(
gpu::GpuMemoryBufferFactory* factory,
VideoPixelFormat pixel_format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
gfx::BufferUsage buffer_usage,
base::ScopedClosureRunner& destroy_cb) {
gfx::GpuMemoryBufferHandle gmb_handle;
auto buffer_format = VideoPixelFormatToGfxBufferFormat(pixel_format);
if (!buffer_format)
return gmb_handle;
if (!factory) {
return GbmDeviceWrapper::Get()->CreateGpuMemoryBuffer(
*buffer_format, coded_size, buffer_usage);
}
gmb_handle = factory->CreateGpuMemoryBuffer(
GetNextGpuMemoryBufferId(), coded_size,
/*framebuffer_size=*/GetRectSizeFromOrigin(visible_rect), *buffer_format,
buffer_usage, gpu::kPlatformVideoFramePoolClientId,
gfx::kNullAcceleratedWidget);
DCHECK(gmb_handle.is_null() || gmb_handle.type != gfx::NATIVE_PIXMAP ||
VideoFrame::NumPlanes(pixel_format) ==
gmb_handle.native_pixmap_handle.planes.size());
if (gmb_handle.is_null())
return gmb_handle;
destroy_cb.ReplaceClosure(
base::BindOnce(&gpu::GpuMemoryBufferFactory::DestroyGpuMemoryBuffer,
base::Unretained(factory), gmb_handle.id,
gpu::kPlatformVideoFramePoolClientId));
return gmb_handle;
}
} // namespace
gfx::GpuMemoryBufferId GetNextGpuMemoryBufferId() {
static base::NoDestructor<base::Lock> id_lock;
static int next_gpu_memory_buffer_id = 0;
base::AutoLock lock(*id_lock);
CHECK_LT(next_gpu_memory_buffer_id, std::numeric_limits<int>::max());
return gfx::GpuMemoryBufferId(next_gpu_memory_buffer_id++);
}
scoped_refptr<VideoFrame> CreateGpuMemoryBufferVideoFrame(
gpu::GpuMemoryBufferFactory* factory,
VideoPixelFormat pixel_format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
base::TimeDelta timestamp,
gfx::BufferUsage buffer_usage) {
base::ScopedClosureRunner destroy_cb((base::DoNothing()));
auto gmb_handle =
AllocateGpuMemoryBufferHandle(factory, pixel_format, coded_size,
visible_rect, buffer_usage, destroy_cb);
if (gmb_handle.is_null() || gmb_handle.type != gfx::NATIVE_PIXMAP)
return nullptr;
auto buffer_format = VideoPixelFormatToGfxBufferFormat(pixel_format);
DCHECK(buffer_format);
gpu::GpuMemoryBufferSupport support;
std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer =
support.CreateGpuMemoryBufferImplFromHandle(
std::move(gmb_handle), coded_size, *buffer_format, buffer_usage,
base::NullCallback());
if (!gpu_memory_buffer)
return nullptr;
// The empty mailbox is ok because this VideoFrame is not rendered.
const gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes] = {};
auto frame = VideoFrame::WrapExternalGpuMemoryBuffer(
visible_rect, natural_size, std::move(gpu_memory_buffer), mailbox_holders,
base::NullCallback(), timestamp);
if (frame)
frame->AddDestructionObserver(destroy_cb.Release());
return frame;
}
scoped_refptr<VideoFrame> CreatePlatformVideoFrame(
gpu::GpuMemoryBufferFactory* factory,
VideoPixelFormat pixel_format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
base::TimeDelta timestamp,
gfx::BufferUsage buffer_usage) {
base::ScopedClosureRunner destroy_cb((base::DoNothing()));
auto gmb_handle =
AllocateGpuMemoryBufferHandle(factory, pixel_format, coded_size,
visible_rect, buffer_usage, destroy_cb);
if (gmb_handle.is_null() || gmb_handle.type != gfx::NATIVE_PIXMAP)
return nullptr;
std::vector<ColorPlaneLayout> planes;
for (const auto& plane : gmb_handle.native_pixmap_handle.planes)
planes.emplace_back(plane.stride, plane.offset, plane.size);
auto layout = VideoFrameLayout::CreateWithPlanes(
pixel_format, coded_size, std::move(planes),
VideoFrameLayout::kBufferAddressAlignment,
gmb_handle.native_pixmap_handle.modifier);
if (!layout)
return nullptr;
std::vector<base::ScopedFD> dmabuf_fds;
for (auto& plane : gmb_handle.native_pixmap_handle.planes)
dmabuf_fds.emplace_back(plane.fd.release());
auto frame = VideoFrame::WrapExternalDmabufs(
*layout, visible_rect, natural_size, std::move(dmabuf_fds), timestamp);
if (!frame)
return nullptr;
frame->AddDestructionObserver(destroy_cb.Release());
return frame;
}
absl::optional<VideoFrameLayout> GetPlatformVideoFrameLayout(
gpu::GpuMemoryBufferFactory* gpu_memory_buffer_factory,
VideoPixelFormat pixel_format,
const gfx::Size& coded_size,
gfx::BufferUsage buffer_usage) {
// |visible_rect| and |natural_size| do not matter here. |coded_size| is set
// as a dummy variable.
auto frame = CreatePlatformVideoFrame(
gpu_memory_buffer_factory, pixel_format, coded_size,
gfx::Rect(coded_size), coded_size, base::TimeDelta(), buffer_usage);
return frame ? absl::make_optional<VideoFrameLayout>(frame->layout())
: absl::nullopt;
}
gfx::GpuMemoryBufferHandle CreateGpuMemoryBufferHandle(
const VideoFrame* video_frame) {
DCHECK(video_frame);
gfx::GpuMemoryBufferHandle handle;
switch (video_frame->storage_type()) {
case VideoFrame::STORAGE_GPU_MEMORY_BUFFER:
handle = video_frame->GetGpuMemoryBuffer()->CloneHandle();
// TODO(crbug.com/1097956): handle a failure gracefully.
CHECK_EQ(handle.type, gfx::NATIVE_PIXMAP)
<< "The cloned handle has an unexpected type: " << handle.type;
CHECK(!handle.native_pixmap_handle.planes.empty())
<< "The cloned handle has no planes";
break;
case VideoFrame::STORAGE_DMABUFS: {
const size_t num_planes = VideoFrame::NumPlanes(video_frame->format());
std::vector<base::ScopedFD> duped_fds =
DuplicateFDs(video_frame->DmabufFds());
// TODO(crbug.com/1036174): Replace this duplication with a check.
// Duplicate the fd of the last plane until the number of fds are the same
// as the number of planes.
while (num_planes != duped_fds.size()) {
int duped_fd = -1;
duped_fd = HANDLE_EINTR(dup(duped_fds.back().get()));
// TODO(crbug.com/1097956): handle a failure gracefully.
PCHECK(duped_fd >= 0) << "Failed duplicating a dma-buf fd";
duped_fds.emplace_back(duped_fd);
}
handle.type = gfx::NATIVE_PIXMAP;
DCHECK_EQ(video_frame->layout().planes().size(), num_planes);
handle.native_pixmap_handle.modifier = video_frame->layout().modifier();
for (size_t i = 0; i < num_planes; ++i) {
const auto& plane = video_frame->layout().planes()[i];
handle.native_pixmap_handle.planes.emplace_back(
plane.stride, plane.offset, plane.size, std::move(duped_fds[i]));
}
} break;
default:
NOTREACHED() << "Unsupported storage type: "
<< video_frame->storage_type();
}
if (!handle.is_null() && handle.type == gfx::NATIVE_PIXMAP &&
!VerifyGpuMemoryBufferHandle(video_frame->format(),
video_frame->coded_size(), handle)) {
VLOGF(1) << "Created GpuMemoryBufferHandle is invalid";
}
return handle;
}
scoped_refptr<gfx::NativePixmapDmaBuf> CreateNativePixmapDmaBuf(
const VideoFrame* video_frame) {
DCHECK(video_frame);
// Create a native pixmap from the frame's memory buffer handle.
gfx::GpuMemoryBufferHandle gpu_memory_buffer_handle =
CreateGpuMemoryBufferHandle(video_frame);
if (gpu_memory_buffer_handle.is_null() ||
gpu_memory_buffer_handle.type != gfx::NATIVE_PIXMAP) {
VLOGF(1) << "Failed to create native GpuMemoryBufferHandle";
return nullptr;
}
auto buffer_format =
VideoPixelFormatToGfxBufferFormat(video_frame->layout().format());
if (!buffer_format) {
VLOGF(1) << "Unexpected video frame format";
return nullptr;
}
auto native_pixmap = base::MakeRefCounted<gfx::NativePixmapDmaBuf>(
video_frame->coded_size(), *buffer_format,
std::move(gpu_memory_buffer_handle.native_pixmap_handle));
DCHECK(native_pixmap->AreDmaBufFdsValid());
return native_pixmap;
}
bool CanImportGpuMemoryBufferHandle(
const gfx::Size& size,
gfx::BufferFormat format,
const gfx::GpuMemoryBufferHandle& gmb_handle) {
if (gmb_handle.type != gfx::GpuMemoryBufferType::NATIVE_PIXMAP) {
VLOGF(1) << "The handle type (" << gmb_handle.type << ") is unsupported";
return false;
}
const auto pixel_format = GfxBufferFormatToVideoPixelFormat(format);
if (!pixel_format) {
VLOGF(1) << "Unsupported buffer format: "
<< gfx::BufferFormatToString(format);
return false;
}
if (!VerifyGpuMemoryBufferHandle(*pixel_format, size, gmb_handle)) {
VLOGF(1) << "Invalid GpuMemoryBufferHandle provided";
return false;
}
gfx::NativePixmapHandle native_pixmap_handle =
gfx::CloneHandleForIPC(gmb_handle.native_pixmap_handle);
if (native_pixmap_handle.planes.empty()) {
VLOGF(1) << "Could not duplicate the NativePixmapHandle";
return false;
}
return !!GbmDeviceWrapper::Get()->ImportGpuMemoryBuffer(
format, size, std::move(native_pixmap_handle));
}
} // namespace media