| // Copyright 2018 The Chromium Authors |
| // 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/containers/fixed_flat_set.h" |
| #include "base/dcheck_is_on.h" |
| #include "base/files/file.h" |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_file.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.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_memory_buffer_support.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_defines.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) { |
| CHECK_LE(handle.planes.size(), base::checked_cast<size_t>(GBM_MAX_PLANES)); |
| 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::EqualsCaseInsensitiveASCII(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( |
| VideoPixelFormat pixel_format, |
| const gfx::Size& coded_size, |
| gfx::BufferUsage buffer_usage) { |
| gfx::GpuMemoryBufferHandle gmb_handle; |
| auto buffer_format = VideoPixelFormatToGfxBufferFormat(pixel_format); |
| if (!buffer_format) |
| return gmb_handle; |
| return GbmDeviceWrapper::Get()->CreateGpuMemoryBuffer( |
| *buffer_format, coded_size, buffer_usage); |
| } |
| } // 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( |
| 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) { |
| auto gmb_handle = |
| AllocateGpuMemoryBufferHandle(pixel_format, coded_size, buffer_usage); |
| if (gmb_handle.is_null() || gmb_handle.type != gfx::NATIVE_PIXMAP) |
| return nullptr; |
| |
| const bool supports_zero_copy_webgpu_import = |
| gmb_handle.native_pixmap_handle.supports_zero_copy_webgpu_import; |
| |
| 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) |
| return nullptr; |
| |
| // We only support importing non-DISJOINT multi-planar GbmBuffer right now. |
| // TODO(crbug.com/1258986): Add DISJOINT support. |
| frame->metadata().is_webgpu_compatible = supports_zero_copy_webgpu_import; |
| |
| return frame; |
| } |
| |
| scoped_refptr<VideoFrame> CreatePlatformVideoFrame( |
| 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) { |
| auto gmb_handle = |
| AllocateGpuMemoryBufferHandle(pixel_format, coded_size, buffer_usage); |
| 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; |
| |
| return frame; |
| } |
| |
| absl::optional<VideoFrameLayout> GetPlatformVideoFrameLayout( |
| 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(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; |
| handle.id = GetNextGpuMemoryBufferId(); |
| 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(); |
| } |
| CHECK_EQ(handle.type, gfx::NATIVE_PIXMAP); |
| if (video_frame->format() == PIXEL_FORMAT_MJPEG) |
| return handle; |
| #if DCHECK_IS_ON() |
| const bool is_handle_valid = |
| !handle.is_null() && |
| VerifyGpuMemoryBufferHandle(video_frame->format(), |
| video_frame->coded_size(), handle); |
| DLOG_IF(WARNING, !is_handle_valid) |
| << __func__ << "(): Created GpuMemoryBufferHandle is invalid"; |
| #endif // DCHECK_IS_ON() |
| 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 |