/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2015 Martin Gräßlin 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 #include #include // c++ #include // drm #include #include 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::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 DrmOutput::getModes() const { bool modeFound = false; QVector 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 &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 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; } }