kwin/src/plugins/platforms/drm/drm_output.cpp
2021-09-16 18:03:14 +02:00

470 lines
13 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_output.h"
#include "drm_backend.h"
#include "drm_object_crtc.h"
#include "drm_object_connector.h"
#include "drm_gpu.h"
#include "drm_pipeline.h"
#include "composite.h"
#include "cursor.h"
#include "logging.h"
#include "main.h"
#include "renderloop.h"
#include "renderloop_p.h"
#include "screens.h"
#include "session.h"
// Qt
#include <QMatrix4x4>
#include <QCryptographicHash>
#include <QPainter>
// c++
#include <cerrno>
// drm
#include <xf86drm.h>
#include <libdrm/drm_mode.h>
namespace KWin
{
DrmOutput::DrmOutput(DrmGpu *gpu, DrmPipeline *pipeline)
: DrmAbstractOutput(gpu)
, m_pipeline(pipeline)
, m_connector(pipeline->connector())
{
m_pipeline->setOutput(this);
auto conn = m_pipeline->connector();
m_renderLoop->setRefreshRate(conn->currentMode().refreshRate);
setSubPixelInternal(conn->subpixel());
setInternal(conn->isInternal());
setCapabilityInternal(DrmOutput::Capability::Dpms);
if (conn->hasOverscan()) {
setCapabilityInternal(Capability::Overscan);
setOverscanInternal(conn->overscan());
}
if (conn->vrrCapable()) {
setCapabilityInternal(Capability::Vrr);
setVrrPolicy(RenderLoop::VrrPolicy::Automatic);
}
if (conn->hasRgbRange()) {
setCapabilityInternal(Capability::RgbRange);
setRgbRangeInternal(conn->rgbRange());
}
initOutputDevice();
m_turnOffTimer.setSingleShot(true);
m_turnOffTimer.setInterval(dimAnimationTime());
connect(&m_turnOffTimer, &QTimer::timeout, this, [this] {
setDrmDpmsMode(DpmsMode::Off);
});
}
DrmOutput::~DrmOutput()
{
hideCursor();
if (m_pageFlipPending) {
pageFlipped();
}
m_pipeline->setOutput(nullptr);
}
bool DrmOutput::initCursor(const QSize &cursorSize)
{
m_cursor = QSharedPointer<DrmDumbBuffer>::create(m_gpu, cursorSize);
if (!m_cursor->map(QImage::Format_ARGB32_Premultiplied)) {
return false;
}
return updateCursor();
}
bool DrmOutput::hideCursor()
{
bool visibleBefore = m_pipeline->isCursorVisible();
if (m_pipeline->setCursor(nullptr)) {
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
&& visibleBefore) {
m_renderLoop->scheduleRepaint();
}
return true;
} else {
return false;
}
}
bool DrmOutput::showCursor()
{
bool visibleBefore = m_pipeline->isCursorVisible();
const Cursor * const cursor = Cursors::self()->currentCursor();
if (m_pipeline->setCursor(m_cursor, logicalToNativeMatrix(cursor->rect(), scale(), transform()).map(cursor->hotspot()) )) {
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
&& !visibleBefore
&& m_pipeline->isCursorVisible()) {
m_renderLoop->scheduleRepaint();
}
return true;
} else {
return false;
}
}
static bool isCursorSpriteCompatible(const QImage *buffer, const QImage *sprite)
{
// Note that we need compare the rects in the device independent pixels because the
// buffer and the cursor sprite image may have different scale factors.
const QRect bufferRect(QPoint(0, 0), buffer->size() / buffer->devicePixelRatio());
const QRect spriteRect(QPoint(0, 0), sprite->size() / sprite->devicePixelRatio());
return bufferRect.contains(spriteRect);
}
bool DrmOutput::updateCursor()
{
const Cursor *cursor = Cursors::self()->currentCursor();
if (!cursor) {
hideCursor();
return true;
}
const QImage cursorImage = cursor->image();
if (cursorImage.isNull()) {
hideCursor();
return true;
}
QImage *c = m_cursor->image();
c->setDevicePixelRatio(scale());
if (!isCursorSpriteCompatible(c, &cursorImage)) {
// If the cursor image is too big, fall back to rendering the software cursor.
return false;
}
c->fill(Qt::transparent);
QPainter p;
p.begin(c);
p.setWorldTransform(logicalToNativeMatrix(cursor->rect(), 1, transform()).toTransform());
p.drawImage(QPoint(0, 0), cursorImage);
p.end();
if (m_pipeline->setCursor(m_cursor, logicalToNativeMatrix(cursor->rect(), scale(), transform()).map(cursor->hotspot()) )) {
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
&& m_pipeline->isCursorVisible()) {
m_renderLoop->scheduleRepaint();
}
return true;
} else {
return false;
}
}
bool DrmOutput::moveCursor()
{
Cursor *cursor = Cursors::self()->currentCursor();
const QMatrix4x4 hotspotMatrix = logicalToNativeMatrix(cursor->rect(), scale(), transform());
const QMatrix4x4 monitorMatrix = logicalToNativeMatrix(geometry(), scale(), transform());
QPoint pos = monitorMatrix.map(cursor->pos());
pos -= hotspotMatrix.map(cursor->hotspot());
bool visibleBefore = m_pipeline->isCursorVisible();
if (pos != m_pipeline->cursorPos()) {
if (!m_pipeline->moveCursor(pos)) {
return false;
}
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
&& (visibleBefore || m_pipeline->isCursorVisible())) {
m_renderLoop->scheduleRepaint();
}
}
return true;
}
QVector<AbstractWaylandOutput::Mode> DrmOutput::getModes() const
{
bool modeFound = false;
QVector<Mode> modes;
auto conn = m_pipeline->connector();
auto modelist = conn->modes();
modes.reserve(modelist.count());
for (int i = 0; i < modelist.count(); ++i) {
Mode mode;
if (i == conn->currentModeIndex()) {
mode.flags |= ModeFlag::Current;
modeFound = true;
}
if (modelist[i].mode.type & DRM_MODE_TYPE_PREFERRED) {
mode.flags |= ModeFlag::Preferred;
}
mode.id = i;
mode.size = modelist[i].size;
mode.refreshRate = modelist[i].refreshRate;
modes << mode;
}
if (!modeFound) {
// select first mode by default
modes[0].flags |= ModeFlag::Current;
}
return modes;
}
void DrmOutput::initOutputDevice()
{
const auto conn = m_pipeline->connector();
setName(conn->connectorName());
initialize(conn->modelName(), conn->edid()->manufacturerString(),
conn->edid()->eisaId(), conn->edid()->serialNumber(),
conn->physicalSize(), getModes(), conn->edid()->raw());
}
void DrmOutput::updateEnablement(bool enable)
{
if (m_pipeline->setActive(enable)) {
m_gpu->platform()->enableOutput(this, enable);
} else {
qCCritical(KWIN_DRM) << "failed to update enablement to" << enable;
}
}
void DrmOutput::setDpmsMode(DpmsMode mode)
{
if (mode == DpmsMode::Off) {
if (!m_turnOffTimer.isActive()) {
Q_EMIT aboutToTurnOff(std::chrono::milliseconds(m_turnOffTimer.interval()));
m_turnOffTimer.start();
}
if (isEnabled()) {
m_gpu->platform()->createDpmsFilter();
}
} else {
m_turnOffTimer.stop();
setDrmDpmsMode(mode);
if (mode != dpmsMode()) {
setDpmsModeInternal(mode);
Q_EMIT wakeUp();
}
}
}
void DrmOutput::setDrmDpmsMode(DpmsMode mode)
{
if (!isEnabled()) {
return;
}
bool active = mode == DpmsMode::On;
if (active == m_pipeline->isActive()) {
setDpmsModeInternal(mode);
return;
}
if (m_pipeline->setActive(active)) {
setDpmsModeInternal(mode);
if (active) {
m_renderLoop->uninhibit();
m_gpu->platform()->checkOutputsAreOn();
if (Compositor *compositor = Compositor::self()) {
compositor->addRepaintFull();
}
} else {
m_renderLoop->inhibit();
m_gpu->platform()->createDpmsFilter();
}
}
}
DrmPlane::Transformations outputToPlaneTransform(DrmOutput::Transform transform)
{
using OutTrans = DrmOutput::Transform;
using PlaneTrans = DrmPlane::Transformation;
// TODO: Do we want to support reflections (flips)?
switch (transform) {
case OutTrans::Normal:
case OutTrans::Flipped:
return PlaneTrans::Rotate0;
case OutTrans::Rotated90:
case OutTrans::Flipped90:
return PlaneTrans::Rotate90;
case OutTrans::Rotated180:
case OutTrans::Flipped180:
return PlaneTrans::Rotate180;
case OutTrans::Rotated270:
case OutTrans::Flipped270:
return PlaneTrans::Rotate270;
default:
Q_UNREACHABLE();
}
}
void DrmOutput::updateTransform(Transform transform)
{
setTransformInternal(transform);
const auto planeTransform = outputToPlaneTransform(transform);
static bool valid;
// If not set or wrong value, assume KWIN_DRM_SW_ROTATIONS_ONLY=1 until DRM transformations are reliable
static int envOnlySoftwareRotations = qEnvironmentVariableIntValue("KWIN_DRM_SW_ROTATIONS_ONLY", &valid) != 0;
if (valid && !envOnlySoftwareRotations && !m_pipeline->setTransformation(planeTransform)) {
qCDebug(KWIN_DRM) << "setting transformation to" << planeTransform << "failed!";
// just in case, if we had any rotation before, clear it
m_pipeline->setTransformation(DrmPlane::Transformation::Rotate0);
}
// show cursor only if is enabled, i.e if pointer device is present
if (!m_gpu->platform()->isCursorHidden() && !m_gpu->platform()->usesSoftwareCursor()) {
// the cursor might need to get rotated
showCursor();
updateCursor();
}
}
void DrmOutput::updateModes()
{
auto conn = m_pipeline->connector();
conn->updateModes();
const auto modes = getModes();
setModes(modes);
auto it = std::find_if(modes.constBegin(), modes.constEnd(),
[](const AbstractWaylandOutput::Mode &mode){
return mode.flags.testFlag(ModeFlag::Current);
}
);
Q_ASSERT(it != modes.constEnd());
AbstractWaylandOutput::Mode mode = *it;
// mode changed
if (mode.size != modeSize() || mode.refreshRate != refreshRate()) {
applyMode(mode.id);
}
}
bool DrmOutput::needsSoftwareTransformation() const
{
return m_pipeline->transformation() != outputToPlaneTransform(transform());
}
void DrmOutput::updateMode(const QSize &size, uint32_t refreshRate)
{
auto conn = m_pipeline->connector();
if (conn->currentMode().size == size && conn->currentMode().refreshRate == refreshRate) {
return;
}
// try to find a fitting mode
auto modelist = conn->modes();
for (int i = 0; i < modelist.size(); i++) {
if (modelist[i].size == size && modelist[i].refreshRate == refreshRate) {
applyMode(i);
return;
}
}
qCWarning(KWIN_DRM, "Could not find a fitting mode with size=%dx%d and refresh rate %d for output %s",
size.width(), size.height(), refreshRate, qPrintable(name()));
}
void DrmOutput::applyMode(int modeIndex)
{
if (m_pipeline->modeset(modeIndex)) {
auto mode = m_pipeline->connector()->currentMode();
AbstractWaylandOutput::setCurrentModeInternal(mode.size, mode.refreshRate);
m_renderLoop->setRefreshRate(mode.refreshRate);
}
}
void DrmOutput::pageFlipped()
{
Q_ASSERT(m_pageFlipPending || !m_gpu->atomicModeSetting());
m_pageFlipPending = false;
m_pipeline->pageFlipped();
}
bool DrmOutput::present(const QSharedPointer<DrmBuffer> &buffer, QRegion damagedRegion)
{
if (!buffer || buffer->bufferId() == 0) {
return false;
}
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop);
if (!m_pipeline->setSyncMode(renderLoopPrivate->presentMode)) {
setVrrPolicy(RenderLoop::VrrPolicy::Never);
}
if (m_pipeline->present(buffer)) {
m_pageFlipPending = true;
Q_EMIT outputChange(damagedRegion);
return true;
} else {
return false;
}
}
int DrmOutput::gammaRampSize() const
{
return m_pipeline->crtc()->gammaRampSize();
}
bool DrmOutput::setGammaRamp(const GammaRamp &gamma)
{
if (m_pipeline->setGammaRamp(gamma)) {
m_renderLoop->scheduleRepaint();
return true;
} else {
return false;
}
}
void DrmOutput::setOverscan(uint32_t overscan)
{
if (m_pipeline->setOverscan(overscan)) {
setOverscanInternal(overscan);
m_renderLoop->scheduleRepaint();
}
}
DrmConnector *DrmOutput::connector() const
{
return m_connector;
}
DrmPipeline *DrmOutput::pipeline() const
{
return m_pipeline;
}
bool DrmOutput::isDpmsEnabled() const
{
return m_pipeline->isActive();
}
QSize DrmOutput::sourceSize() const
{
return m_pipeline->sourceSize();
}
bool DrmOutput::isFormatSupported(uint32_t drmFormat) const
{
return m_pipeline->isFormatSupported(drmFormat);
}
QVector<uint64_t> DrmOutput::supportedModifiers(uint32_t drmFormat) const
{
return m_pipeline->supportedModifiers(drmFormat);
}
void DrmOutput::setRgbRange(RgbRange range)
{
if (m_pipeline->setRgbRange(range)) {
setRgbRangeInternal(range);
m_renderLoop->scheduleRepaint();
}
}
void DrmOutput::setPipeline(DrmPipeline *pipeline)
{
Q_ASSERT_X(!pipeline || pipeline->connector() == m_connector, "DrmOutput::setPipeline", "Pipeline with wrong connector set!");
m_pipeline = pipeline;
}
}