From patchwork Tue May 19 03:25:03 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3817 X-Patchwork-Delegate: laurent.pinchart@ideasonboard.com Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CA2FA60E2A for ; Tue, 19 May 2020 05:25:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="PzyQVeJB"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 66F96A1D for ; Tue, 19 May 2020 05:25:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1589858720; bh=hJfRUAq3ru0Az/WIoLLsQIJq1KuWUmXCJ594x3p2dPU=; h=From:To:Subject:Date:In-Reply-To:References:From; b=PzyQVeJBzklx6fGbC080YZjLLDIguAPC81efJIglrzmO/ARHrj/UbN2PTgrQpUefw Z8g7Efnx5XRQQqtu6loMgF8bggdUq9uA5bwFWyPgzWUZZ9v5wi2Hhp//9XnPDU19fG Sx2JmwU7Xt1jZGxNQBWE1loQ7zTqLy1KZbD/V3DU= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Tue, 19 May 2020 06:25:03 +0300 Message-Id: <20200519032505.17307-7-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200519032505.17307-1-laurent.pinchart@ideasonboard.com> References: <20200519032505.17307-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 6/8] cam: Add KMS sink class X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 19 May 2020 03:25:21 -0000 Add a KMSSink class to display framebuffers through the DRM/KMS API. Signed-off-by: Laurent Pinchart --- src/cam/kms_sink.cpp | 297 +++++++++++++++++++++++++++++++++++++++++++ src/cam/kms_sink.h | 76 +++++++++++ src/cam/meson.build | 1 + 3 files changed, 374 insertions(+) create mode 100644 src/cam/kms_sink.cpp create mode 100644 src/cam/kms_sink.h diff --git a/src/cam/kms_sink.cpp b/src/cam/kms_sink.cpp new file mode 100644 index 000000000000..85f244ea5413 --- /dev/null +++ b/src/cam/kms_sink.cpp @@ -0,0 +1,297 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Ideas on Board Oy + * + * kms_sink.cpp - KMS Sink + */ + +#include "kms_sink.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "drm.h" + +KMSSink::KMSSink(const std::string &connectorName) + : connector_(nullptr), crtc_(nullptr), plane_(nullptr), mode_(nullptr) +{ + int ret = dev_.init(); + if (ret < 0) + return; + + /* + * Find the requested connector. If no connector is requested, pick the + * first connected connector. + */ + for (const DRM::Connector &conn : dev_.connectors()) { + if (conn.name() == connectorName) { + connector_ = &conn; + break; + } + + if (conn.status() != DRM::Connector::Disconnected) { + if (!connector_ || + (connector_->status() == DRM::Connector::Unknown && + conn.status() == DRM::Connector::Connected)) + connector_ = &conn; + } + } + + if (!connector_) { + if (!connectorName.empty()) + std::cerr + << "Connector " << connectorName << " not found" + << std::endl; + else + std::cerr << "No connected connector found" << std::endl; + return; + } + + dev_.requestComplete.connect(this, &KMSSink::requestComplete); +} + +void KMSSink::mapBuffer(libcamera::FrameBuffer *buffer) +{ + std::unique_ptr drmBuffer = + dev_.createFrameBuffer(*buffer, format_, size_, stride_); + if (!drmBuffer) + return; + + buffers_.emplace(std::piecewise_construct, + std::forward_as_tuple(buffer), + std::forward_as_tuple(std::move(drmBuffer))); +} + +int KMSSink::configure(const libcamera::CameraConfiguration &config) +{ + crtc_ = nullptr; + plane_ = nullptr; + mode_ = nullptr; + + const libcamera::StreamConfiguration &cfg = config.at(0); + int ret = configurePipeline(cfg.pixelFormat); + if (ret < 0) + return ret; + + const std::vector &modes = connector_->modes(); + const auto iter = std::find_if(modes.begin(), modes.end(), + [&](const DRM::Mode &mode) { + return mode.hdisplay == cfg.size.width && + mode.vdisplay == cfg.size.height; + }); + if (iter == modes.end()) { + std::cerr + << "No mode matching " << cfg.size.toString() + << std::endl; + return -EINVAL; + } + + mode_ = &*iter; + size_ = cfg.size; + stride_ = cfg.stride; + + return 0; +} + +int KMSSink::configurePipeline(const libcamera::PixelFormat &format) +{ + /* + * If the requested format has an alpha channel, also consider the X + * variant. + */ + libcamera::PixelFormat xFormat; + + switch (format) { + case DRM_FORMAT_ABGR8888: + xFormat = libcamera::PixelFormat(DRM_FORMAT_XBGR8888); + break; + case DRM_FORMAT_ARGB8888: + xFormat = libcamera::PixelFormat(DRM_FORMAT_XRGB8888); + break; + case DRM_FORMAT_BGRA8888: + xFormat = libcamera::PixelFormat(DRM_FORMAT_BGRX8888); + break; + case DRM_FORMAT_RGBA8888: + xFormat = libcamera::PixelFormat(DRM_FORMAT_RGBX8888); + break; + } + + /* + * Find a CRTC and plane suitable for the request format and the + * connector at the end of the pipeline. Restrict the search to primary + * planes for now. + */ + for (const DRM::Encoder *encoder : connector_->encoders()) { + for (const DRM::Crtc *crtc : encoder->possibleCrtcs()) { + for (const DRM::Plane *plane : crtc->planes()) { + if (plane->type() != DRM::Plane::TypePrimary) + continue; + + if (plane->supportsFormat(format)) { + crtc_ = crtc; + plane_ = plane; + format_ = format; + return 0; + } + + if (plane->supportsFormat(xFormat)) { + crtc_ = crtc; + plane_ = plane; + format_ = xFormat; + return 0; + } + } + } + } + + std::cerr + << "Unable to find display pipeline for format " + << format.toString() << std::endl; + return -EPIPE; +} + +int KMSSink::start() +{ + std::unique_ptr request; + + int ret = FrameSink::start(); + if (ret < 0) + return ret; + + /* Disable all CRTCs and planes to start from a known valid state. */ + request = std::make_unique(&dev_); + + for (const DRM::Crtc &crtc : dev_.crtcs()) + request->addProperty(&crtc, "ACTIVE", 0); + + for (const DRM::Plane &plane : dev_.planes()) { + request->addProperty(&plane, "CRTC_ID", 0); + request->addProperty(&plane, "FB_ID", 0); + } + + ret = request->commit(DRM::AtomicRequest::FlagAllowModeset); + if (ret < 0) { + std::cerr + << "Failed to disable CRTCs and planes: " + << strerror(-ret) << std::endl; + return ret; + } + + /* Enable the display pipeline with no plane to start with. */ + request = std::make_unique(&dev_); + + request->addProperty(connector_, "CRTC_ID", crtc_->id()); + request->addProperty(crtc_, "ACTIVE", 1); + request->addProperty(crtc_, "MODE_ID", mode_->toBlob(&dev_)); + + ret = request->commit(DRM::AtomicRequest::FlagAllowModeset); + if (ret < 0) { + std::cerr + << "Failed to enable display pipeline: " + << strerror(-ret) << std::endl; + return ret; + } + + planeInitialized_ = false; + + return 0; +} + +int KMSSink::stop() +{ + /* Display pipeline. */ + DRM::AtomicRequest request(&dev_); + + request.addProperty(connector_, "CRTC_ID", 0); + request.addProperty(crtc_, "ACTIVE", 0); + request.addProperty(crtc_, "MODE_ID", 0); + request.addProperty(plane_, "CRTC_ID", 0); + request.addProperty(plane_, "FB_ID", 0); + + int ret = request.commit(DRM::AtomicRequest::FlagAllowModeset); + if (ret < 0) { + std::cerr + << "Failed to stop display pipeline: " + << strerror(-ret) << std::endl; + return ret; + } + + /* Free all buffers. */ + pending_.reset(); + queued_.reset(); + active_.reset(); + buffers_.clear(); + + return FrameSink::stop(); +} + +bool KMSSink::consumeBuffer(const libcamera::Stream *stream, + libcamera::FrameBuffer *buffer) +{ + if (pending_) + return true; + + auto iter = buffers_.find(buffer); + if (iter == buffers_.end()) + return true; + + DRM::FrameBuffer *drmBuffer = iter->second.get(); + + DRM::AtomicRequest *request = new DRM::AtomicRequest(&dev_); + request->addProperty(plane_, "FB_ID", drmBuffer->id()); + + if (!planeInitialized_) { + request->addProperty(plane_, "CRTC_ID", crtc_->id()); + request->addProperty(plane_, "SRC_X", 0 << 16); + request->addProperty(plane_, "SRC_Y", 0 << 16); + request->addProperty(plane_, "SRC_W", mode_->hdisplay << 16); + request->addProperty(plane_, "SRC_H", mode_->vdisplay << 16); + request->addProperty(plane_, "CRTC_X", 0); + request->addProperty(plane_, "CRTC_Y", 0); + request->addProperty(plane_, "CRTC_W", mode_->hdisplay); + request->addProperty(plane_, "CRTC_H", mode_->vdisplay); + planeInitialized_ = true; + } + + pending_ = std::make_unique(request, buffer); + + std::lock_guard lock(lock_); + + if (!queued_) { + int ret = request->commit(DRM::AtomicRequest::FlagAsync); + if (ret < 0) + std::cerr + << "Failed to commit atomic request: " + << strerror(-ret) << std::endl; + queued_ = std::move(pending_); + } + + return false; +} + +void KMSSink::requestComplete(DRM::AtomicRequest *request) +{ + std::lock_guard lock(lock_); + + assert(queued_ && queued_->request_.get() == request); + + /* Complete the active request, if any. */ + if (active_) + bufferReleased.emit(active_->buffer_); + + /* The queued request becomes active. */ + active_ = std::move(queued_); + + /* Queue the pending request, if any. */ + if (pending_) { + pending_->request_->commit(DRM::AtomicRequest::FlagAsync); + queued_ = std::move(pending_); + } +} diff --git a/src/cam/kms_sink.h b/src/cam/kms_sink.h new file mode 100644 index 000000000000..ee257a071c24 --- /dev/null +++ b/src/cam/kms_sink.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Ideas on Board Oy + * + * kms_sink.h - KMS Sink + */ +#ifndef __CAM_KMS_SINK_H__ +#define __CAM_KMS_SINK_H__ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "drm.h" +#include "frame_sink.h" + +class KMSSink : public FrameSink +{ +public: + KMSSink(const std::string &connectorName); + + bool isValid() const { return connector_ != nullptr; } + + void mapBuffer(libcamera::FrameBuffer *buffer) override; + + int configure(const libcamera::CameraConfiguration &config) override; + int start() override; + int stop() override; + + bool consumeBuffer(const libcamera::Stream *stream, + libcamera::FrameBuffer *buffer) override; + +private: + class Request + { + public: + Request(DRM::AtomicRequest *request, libcamera::FrameBuffer *buffer) + : request_(request), buffer_(buffer) + { + } + + std::unique_ptr request_; + libcamera::FrameBuffer *buffer_; + }; + + int configurePipeline(const libcamera::PixelFormat &format); + void requestComplete(DRM::AtomicRequest *request); + + DRM::Device dev_; + + const DRM::Connector *connector_; + const DRM::Crtc *crtc_; + const DRM::Plane *plane_; + const DRM::Mode *mode_; + + libcamera::PixelFormat format_; + libcamera::Size size_; + unsigned int stride_; + + bool planeInitialized_; + + std::map> buffers_; + + std::mutex lock_; + std::unique_ptr pending_; + std::unique_ptr queued_; + std::unique_ptr active_; +}; + +#endif /* __CAM_KMS_SINK_H__ */ diff --git a/src/cam/meson.build b/src/cam/meson.build index b0a8a7580dcc..55f37ed7b323 100644 --- a/src/cam/meson.build +++ b/src/cam/meson.build @@ -18,6 +18,7 @@ if libdrm.found() cam_cpp_args += [ '-DHAVE_KMS' ] cam_sources += files([ 'drm.cpp', + 'kms_sink.cpp' ]) endif