935 lines
28 KiB
C++
935 lines
28 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 "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>
|
|
|
|
#include "drm_gpu.h"
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
DrmOutput::DrmOutput(DrmBackend *backend, DrmGpu *gpu)
|
|
: AbstractWaylandOutput(backend)
|
|
, m_backend(backend)
|
|
, m_gpu(gpu)
|
|
, m_renderLoop(new RenderLoop(this))
|
|
{
|
|
}
|
|
|
|
DrmOutput::~DrmOutput()
|
|
{
|
|
Q_ASSERT(!m_pageFlipPending);
|
|
teardown();
|
|
}
|
|
|
|
RenderLoop *DrmOutput::renderLoop() const
|
|
{
|
|
return m_renderLoop;
|
|
}
|
|
|
|
void DrmOutput::teardown()
|
|
{
|
|
if (m_deleted) {
|
|
return;
|
|
}
|
|
m_deleted = true;
|
|
hideCursor();
|
|
m_crtc->blank(this);
|
|
|
|
if (m_primaryPlane) {
|
|
// TODO: when having multiple planes, also clean up these
|
|
m_primaryPlane->setCurrent(nullptr);
|
|
}
|
|
|
|
m_cursor[0].reset(nullptr);
|
|
m_cursor[1].reset(nullptr);
|
|
if (!m_pageFlipPending) {
|
|
deleteLater();
|
|
} //else will be deleted in the page flip handler
|
|
//this is needed so that the pageflipcallback handle isn't deleted
|
|
}
|
|
|
|
void DrmOutput::releaseGbm()
|
|
{
|
|
if (auto buffer = m_crtc->current()) {
|
|
buffer->releaseGbm();
|
|
}
|
|
if (auto buffer = m_crtc->next()) {
|
|
buffer->releaseGbm();
|
|
}
|
|
if (m_primaryPlane) {
|
|
if (auto buffer = m_primaryPlane->current()) {
|
|
buffer->releaseGbm();
|
|
}
|
|
if (auto buffer = m_primaryPlane->next()) {
|
|
buffer->releaseGbm();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool DrmOutput::hideCursor()
|
|
{
|
|
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
|
|
&& isCursorVisible()) {
|
|
m_renderLoop->scheduleRepaint();
|
|
}
|
|
return drmModeSetCursor(m_gpu->fd(), m_crtc->id(), 0, 0, 0) == 0;
|
|
}
|
|
|
|
bool DrmOutput::showCursor(DrmDumbBuffer *c)
|
|
{
|
|
const QSize &s = c->size();
|
|
if (drmModeSetCursor(m_gpu->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0) {
|
|
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
|
|
&& isCursorVisible()) {
|
|
m_renderLoop->scheduleRepaint();
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool DrmOutput::showCursor()
|
|
{
|
|
if (m_deleted) {
|
|
return false;
|
|
}
|
|
|
|
const bool ret = showCursor(m_cursor[m_cursorIndex].data());
|
|
if (!ret) {
|
|
qCDebug(KWIN_DRM) << "DrmOutput::showCursor(DrmDumbBuffer) failed";
|
|
return ret;
|
|
}
|
|
|
|
bool visibleBefore = isCursorVisible();
|
|
if (m_hasNewCursor) {
|
|
m_cursorIndex = (m_cursorIndex + 1) % 2;
|
|
m_hasNewCursor = false;
|
|
}
|
|
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
|
|
&& !visibleBefore
|
|
&& isCursorVisible()) {
|
|
m_renderLoop->scheduleRepaint();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
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()
|
|
{
|
|
if (m_deleted) {
|
|
return false;
|
|
}
|
|
const Cursor *cursor = Cursors::self()->currentCursor();
|
|
const QImage cursorImage = cursor->image();
|
|
if (cursorImage.isNull()) {
|
|
return false;
|
|
}
|
|
|
|
QImage *c = m_cursor[m_cursorIndex]->image();
|
|
c->setDevicePixelRatio(scale());
|
|
|
|
if (!isCursorSpriteCompatible(c, &cursorImage)) {
|
|
// If the cursor image is too big, fall back to rendering the software cursor.
|
|
return false;
|
|
}
|
|
|
|
m_hasNewCursor = true;
|
|
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 (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
|
|
&& isCursorVisible()) {
|
|
m_renderLoop->scheduleRepaint();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void 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());
|
|
|
|
if (pos != m_cursorPos) {
|
|
bool visible = isCursorVisible();
|
|
drmModeMoveCursor(m_gpu->fd(), m_crtc->id(), pos.x(), pos.y());
|
|
m_cursorPos = pos;
|
|
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
|
|
&& (visible || visible != isCursorVisible())) {
|
|
m_renderLoop->scheduleRepaint();
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
quint64 refreshRateForMode(_drmModeModeInfo *m)
|
|
{
|
|
// Calculate higher precision (mHz) refresh rate
|
|
// logic based on Weston, see compositor-drm.c
|
|
quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal;
|
|
if (m->flags & DRM_MODE_FLAG_INTERLACE) {
|
|
refreshRate *= 2;
|
|
}
|
|
if (m->flags & DRM_MODE_FLAG_DBLSCAN) {
|
|
refreshRate /= 2;
|
|
}
|
|
if (m->vscan > 1) {
|
|
refreshRate /= m->vscan;
|
|
}
|
|
return refreshRate;
|
|
}
|
|
}
|
|
|
|
static AbstractWaylandOutput::SubPixel drmSubPixelToKWinSubPixel(drmModeSubPixel subpixel)
|
|
{
|
|
switch (subpixel) {
|
|
case DRM_MODE_SUBPIXEL_UNKNOWN:
|
|
return AbstractWaylandOutput::SubPixel::Unknown;
|
|
case DRM_MODE_SUBPIXEL_NONE:
|
|
return AbstractWaylandOutput::SubPixel::None;
|
|
case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
|
|
return AbstractWaylandOutput::SubPixel::Horizontal_RGB;
|
|
case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
|
|
return AbstractWaylandOutput::SubPixel::Horizontal_BGR;
|
|
case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
|
|
return AbstractWaylandOutput::SubPixel::Vertical_RGB;
|
|
case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
|
|
return AbstractWaylandOutput::SubPixel::Vertical_BGR;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
bool DrmOutput::init(drmModeConnector *connector)
|
|
{
|
|
if (m_gpu->atomicModeSetting() && !m_primaryPlane) {
|
|
return false;
|
|
}
|
|
|
|
setSubPixelInternal(drmSubPixelToKWinSubPixel(connector->subpixel));
|
|
setInternal(m_conn->isInternal());
|
|
setCapabilityInternal(DrmOutput::Capability::Dpms);
|
|
if (m_conn->hasOverscan()) {
|
|
setCapabilityInternal(Capability::Overscan);
|
|
setOverscanInternal(m_conn->overscan());
|
|
}
|
|
if (m_conn->vrrCapable()) {
|
|
setCapabilityInternal(Capability::Vrr);
|
|
setVrrPolicy(RenderLoop::VrrPolicy::Automatic);
|
|
}
|
|
initOutputDevice(connector);
|
|
|
|
if (!m_gpu->atomicModeSetting() && !m_crtc->blank(this)) {
|
|
// We use legacy mode and the initial output blank failed.
|
|
return false;
|
|
}
|
|
|
|
setDpmsMode(DpmsMode::On);
|
|
return true;
|
|
}
|
|
|
|
void DrmOutput::initOutputDevice(drmModeConnector *connector)
|
|
{
|
|
// read in mode information
|
|
QVector<Mode> modes;
|
|
modes.reserve(connector->count_modes);
|
|
for (int i = 0; i < connector->count_modes; ++i) {
|
|
// TODO: in AMS here we could read and store for later every mode's blob_id
|
|
// would simplify isCurrentMode(..) and presentAtomically(..) in case of mode set
|
|
auto *m = &connector->modes[i];
|
|
|
|
Mode mode;
|
|
if (isCurrentMode(m)) {
|
|
mode.flags |= ModeFlag::Current;
|
|
}
|
|
if (m->type & DRM_MODE_TYPE_PREFERRED) {
|
|
mode.flags |= ModeFlag::Preferred;
|
|
}
|
|
|
|
mode.id = i;
|
|
mode.size = QSize(m->hdisplay, m->vdisplay);
|
|
mode.refreshRate = refreshRateForMode(m);
|
|
modes << mode;
|
|
}
|
|
|
|
setName(m_conn->connectorName());
|
|
initialize(m_conn->modelName(), m_conn->edid()->manufacturerString(),
|
|
m_conn->edid()->eisaId(), m_conn->edid()->serialNumber(),
|
|
m_conn->physicalSize(), modes, m_conn->edid()->raw());
|
|
}
|
|
|
|
bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const
|
|
{
|
|
return mode->clock == m_mode.clock
|
|
&& mode->hdisplay == m_mode.hdisplay
|
|
&& mode->hsync_start == m_mode.hsync_start
|
|
&& mode->hsync_end == m_mode.hsync_end
|
|
&& mode->htotal == m_mode.htotal
|
|
&& mode->hskew == m_mode.hskew
|
|
&& mode->vdisplay == m_mode.vdisplay
|
|
&& mode->vsync_start == m_mode.vsync_start
|
|
&& mode->vsync_end == m_mode.vsync_end
|
|
&& mode->vtotal == m_mode.vtotal
|
|
&& mode->vscan == m_mode.vscan
|
|
&& mode->vrefresh == m_mode.vrefresh
|
|
&& mode->flags == m_mode.flags
|
|
&& mode->type == m_mode.type
|
|
&& qstrcmp(mode->name, m_mode.name) == 0;
|
|
}
|
|
|
|
bool DrmOutput::initCursor(const QSize &cursorSize)
|
|
{
|
|
auto createCursor = [this, cursorSize] (int index) {
|
|
m_cursor[index].reset(new DrmDumbBuffer(m_gpu, cursorSize));
|
|
if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
if (!createCursor(0) || !createCursor(1)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void DrmOutput::updateEnablement(bool enable)
|
|
{
|
|
if (enable) {
|
|
m_dpmsModePending = DpmsMode::On;
|
|
if (m_gpu->atomicModeSetting()) {
|
|
atomicEnable();
|
|
} else {
|
|
if (dpmsLegacyApply()) {
|
|
m_backend->enableOutput(this, true);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
m_dpmsModePending = DpmsMode::Off;
|
|
if (m_gpu->atomicModeSetting()) {
|
|
atomicDisable();
|
|
} else {
|
|
if (dpmsLegacyApply()) {
|
|
m_backend->enableOutput(this, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrmOutput::atomicEnable()
|
|
{
|
|
m_modesetRequested = true;
|
|
|
|
if (m_atomicOffPending) {
|
|
Q_ASSERT(m_pageFlipPending);
|
|
m_atomicOffPending = false;
|
|
}
|
|
m_backend->enableOutput(this, true);
|
|
dpmsFinishOn();
|
|
|
|
if (Compositor *compositor = Compositor::self()) {
|
|
compositor->addRepaintFull();
|
|
}
|
|
}
|
|
|
|
void DrmOutput::atomicDisable()
|
|
{
|
|
m_modesetRequested = true;
|
|
|
|
m_backend->enableOutput(this, false);
|
|
m_atomicOffPending = true;
|
|
if (!m_pageFlipPending) {
|
|
dpmsAtomicOff();
|
|
}
|
|
}
|
|
|
|
void DrmOutput::setDpmsMode(DpmsMode mode)
|
|
{
|
|
if (!m_conn->dpms() || !isEnabled()) {
|
|
return;
|
|
}
|
|
|
|
if (mode == m_dpmsModePending) {
|
|
return;
|
|
}
|
|
|
|
m_dpmsModePending = mode;
|
|
|
|
if (m_gpu->atomicModeSetting()) {
|
|
m_modesetRequested = true;
|
|
if (mode == DpmsMode::On) {
|
|
if (m_atomicOffPending) {
|
|
Q_ASSERT(m_pageFlipPending);
|
|
m_atomicOffPending = false;
|
|
}
|
|
dpmsFinishOn();
|
|
} else {
|
|
m_atomicOffPending = true;
|
|
if (!m_pageFlipPending) {
|
|
dpmsAtomicOff();
|
|
}
|
|
}
|
|
} else {
|
|
dpmsLegacyApply();
|
|
}
|
|
}
|
|
|
|
void DrmOutput::dpmsFinishOn()
|
|
{
|
|
qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On.";
|
|
|
|
m_backend->checkOutputsAreOn();
|
|
m_crtc->blank(this);
|
|
m_renderLoop->uninhibit();
|
|
if (Compositor *compositor = Compositor::self()) {
|
|
compositor->addRepaintFull();
|
|
}
|
|
}
|
|
|
|
void DrmOutput::dpmsFinishOff()
|
|
{
|
|
qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off.";
|
|
|
|
if (isEnabled()) {
|
|
m_backend->createDpmsFilter();
|
|
}
|
|
m_renderLoop->inhibit();
|
|
}
|
|
|
|
static uint64_t kwinDpmsModeToDrmDpmsMode(AbstractWaylandOutput::DpmsMode dpmsMode)
|
|
{
|
|
switch (dpmsMode) {
|
|
case AbstractWaylandOutput::DpmsMode::On:
|
|
return DRM_MODE_DPMS_ON;
|
|
case AbstractWaylandOutput::DpmsMode::Standby:
|
|
return DRM_MODE_DPMS_STANDBY;
|
|
case AbstractWaylandOutput::DpmsMode::Suspend:
|
|
return DRM_MODE_DPMS_SUSPEND;
|
|
case AbstractWaylandOutput::DpmsMode::Off:
|
|
return DRM_MODE_DPMS_OFF;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
bool DrmOutput::dpmsLegacyApply()
|
|
{
|
|
if (drmModeConnectorSetProperty(m_gpu->fd(), m_conn->id(),
|
|
m_conn->dpms()->propId(),
|
|
kwinDpmsModeToDrmDpmsMode(m_dpmsModePending)) < 0) {
|
|
m_dpmsModePending = dpmsMode();
|
|
qCWarning(KWIN_DRM) << "Setting DPMS failed";
|
|
return false;
|
|
}
|
|
if (m_dpmsModePending == DpmsMode::On) {
|
|
dpmsFinishOn();
|
|
} else {
|
|
dpmsFinishOff();
|
|
}
|
|
setDpmsModeInternal(m_dpmsModePending);
|
|
return true;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
bool DrmOutput::hardwareTransforms() const
|
|
{
|
|
if (!m_primaryPlane) {
|
|
return false;
|
|
}
|
|
return m_primaryPlane->transformation() == outputToPlaneTransform(transform());
|
|
}
|
|
|
|
void DrmOutput::updateTransform(Transform transform)
|
|
{
|
|
const auto planeTransform = outputToPlaneTransform(transform);
|
|
|
|
if (m_primaryPlane) {
|
|
// At the moment we have to exclude hardware transforms for vertical buffers.
|
|
// For that we need to support other buffers and graceful fallback from atomic tests.
|
|
// Reason is that standard linear buffers are not suitable.
|
|
const bool isPortrait = transform == Transform::Rotated90
|
|
|| transform == Transform::Flipped90
|
|
|| transform == Transform::Rotated270
|
|
|| transform == Transform::Flipped270;
|
|
|
|
if (!qEnvironmentVariableIsSet("KWIN_DRM_SW_ROTATIONS_ONLY") &&
|
|
(m_primaryPlane->supportedTransformations() & planeTransform) &&
|
|
!isPortrait) {
|
|
m_primaryPlane->setTransformation(planeTransform);
|
|
} else {
|
|
m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate0);
|
|
}
|
|
}
|
|
m_modesetRequested = true;
|
|
|
|
// show cursor only if is enabled, i.e if pointer device is presentP
|
|
if (!m_backend->isCursorHidden() && !m_backend->usesSoftwareCursor()) {
|
|
// the cursor might need to get rotated
|
|
updateCursor();
|
|
showCursor();
|
|
}
|
|
}
|
|
|
|
void DrmOutput::updateMode(uint32_t width, uint32_t height, uint32_t refreshRate)
|
|
{
|
|
if (m_mode.hdisplay == width && m_mode.vdisplay == height && refreshRateForMode(&m_mode) == refreshRate) {
|
|
return;
|
|
}
|
|
// try to find a fitting mode
|
|
DrmScopedPointer<drmModeConnector> connector(drmModeGetConnectorCurrent(m_gpu->fd(), m_conn->id()));
|
|
for (int i = 0; i < connector->count_modes; i++) {
|
|
auto mode = connector->modes[i];
|
|
if (mode.hdisplay == width && mode.vdisplay == height && refreshRateForMode(&mode) == refreshRate) {
|
|
updateMode(i);
|
|
return;
|
|
}
|
|
}
|
|
qCWarning(KWIN_DRM, "Could not find a fitting mode with size=%dx%d and refresh rate %d for output %s",
|
|
width, height, refreshRate, qPrintable(name()));
|
|
}
|
|
|
|
void DrmOutput::updateMode(int modeIndex)
|
|
{
|
|
// get all modes on the connector
|
|
DrmScopedPointer<drmModeConnector> connector(drmModeGetConnector(m_gpu->fd(), m_conn->id()));
|
|
if (connector->count_modes <= modeIndex) {
|
|
// TODO: error?
|
|
return;
|
|
}
|
|
if (isCurrentMode(&connector->modes[modeIndex])) {
|
|
// nothing to do
|
|
return;
|
|
}
|
|
m_mode = connector->modes[modeIndex];
|
|
m_modesetRequested = true;
|
|
setCurrentModeInternal();
|
|
}
|
|
|
|
void DrmOutput::setCurrentModeInternal()
|
|
{
|
|
AbstractWaylandOutput::setCurrentModeInternal(QSize(m_mode.hdisplay, m_mode.vdisplay),
|
|
refreshRateForMode(&m_mode));
|
|
}
|
|
|
|
void DrmOutput::pageFlipped()
|
|
{
|
|
// In legacy mode we might get a page flip through a blank.
|
|
Q_ASSERT(m_pageFlipPending || !m_gpu->atomicModeSetting());
|
|
m_pageFlipPending = false;
|
|
|
|
if (m_deleted) {
|
|
deleteLater();
|
|
return;
|
|
}
|
|
|
|
if (!m_crtc) {
|
|
return;
|
|
}
|
|
if (m_gpu->atomicModeSetting()) {
|
|
for (DrmPlane *p : qAsConst(m_nextPlanesFlipList)) {
|
|
p->flipBuffer();
|
|
}
|
|
m_nextPlanesFlipList.clear();
|
|
} else {
|
|
m_crtc->flipBuffer();
|
|
}
|
|
|
|
if (m_atomicOffPending) {
|
|
dpmsAtomicOff();
|
|
}
|
|
}
|
|
|
|
bool DrmOutput::present(const QSharedPointer<DrmBuffer> &buffer)
|
|
{
|
|
if (!buffer || buffer->bufferId() == 0) {
|
|
return false;
|
|
}
|
|
if (m_dpmsModePending != DpmsMode::On) {
|
|
return false;
|
|
}
|
|
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop);
|
|
setVrr(renderLoopPrivate->presentMode == RenderLoopPrivate::SyncMode::Adaptive);
|
|
return m_gpu->atomicModeSetting() ? presentAtomically(buffer) : presentLegacy(buffer);
|
|
}
|
|
|
|
bool DrmOutput::dpmsAtomicOff()
|
|
{
|
|
m_atomicOffPending = false;
|
|
|
|
// TODO: With multiple planes: deactivate all of them here
|
|
hideCursor();
|
|
m_primaryPlane->setNext(nullptr);
|
|
m_nextPlanesFlipList << m_primaryPlane;
|
|
|
|
if (!doAtomicCommit(AtomicCommitMode::Test)) {
|
|
qCDebug(KWIN_DRM) << "Atomic test commit to Dpms Off failed. Aborting.";
|
|
return false;
|
|
}
|
|
if (!doAtomicCommit(AtomicCommitMode::Real)) {
|
|
qCDebug(KWIN_DRM) << "Atomic commit to Dpms Off failed. This should have never happened! Aborting.";
|
|
return false;
|
|
}
|
|
m_nextPlanesFlipList.clear();
|
|
dpmsFinishOff();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DrmOutput::presentAtomically(const QSharedPointer<DrmBuffer> &buffer)
|
|
{
|
|
if (!m_backend->session()->isActive()) {
|
|
qCWarning(KWIN_DRM) << "Refusing to present output because session is inactive";
|
|
return false;
|
|
}
|
|
|
|
if (m_pageFlipPending) {
|
|
qCWarning(KWIN_DRM) << "Page not yet flipped.";
|
|
return false;
|
|
}
|
|
|
|
#if HAVE_EGL_STREAMS
|
|
if (m_gpu->useEglStreams() && !m_modesetRequested) {
|
|
// EglStreamBackend queues normal page flips through EGL,
|
|
// modesets are still performed through DRM-KMS
|
|
m_pageFlipPending = true;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
m_primaryPlane->setNext(buffer);
|
|
m_nextPlanesFlipList << m_primaryPlane;
|
|
|
|
if (!doAtomicCommit(AtomicCommitMode::Test)) {
|
|
//TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout?
|
|
//TODO: Probably should undo setNext and reset the flip list
|
|
qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present.";
|
|
// go back to previous state
|
|
if (m_lastWorkingState.valid) {
|
|
m_mode = m_lastWorkingState.mode;
|
|
setTransformInternal(m_lastWorkingState.transform);
|
|
setGlobalPos(m_lastWorkingState.globalPos);
|
|
if (m_primaryPlane) {
|
|
m_primaryPlane->setTransformation(m_lastWorkingState.planeTransformations);
|
|
}
|
|
m_modesetRequested = true;
|
|
if (!m_backend->isCursorHidden()) {
|
|
// the cursor might need to get rotated
|
|
updateCursor();
|
|
showCursor();
|
|
}
|
|
setCurrentModeInternal();
|
|
emit screens()->changed();
|
|
}
|
|
return false;
|
|
}
|
|
const bool wasModeset = m_modesetRequested;
|
|
if (!doAtomicCommit(AtomicCommitMode::Real)) {
|
|
qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present.";
|
|
//TODO: Probably should undo setNext and reset the flip list
|
|
return false;
|
|
}
|
|
if (wasModeset) {
|
|
// store current mode set as new good state
|
|
m_lastWorkingState.mode = m_mode;
|
|
m_lastWorkingState.transform = transform();
|
|
m_lastWorkingState.globalPos = globalPos();
|
|
if (m_primaryPlane) {
|
|
m_lastWorkingState.planeTransformations = m_primaryPlane->transformation();
|
|
}
|
|
m_lastWorkingState.valid = true;
|
|
m_renderLoop->setRefreshRate(refreshRateForMode(&m_mode));
|
|
}
|
|
m_pageFlipPending = true;
|
|
return true;
|
|
}
|
|
|
|
bool DrmOutput::presentLegacy(const QSharedPointer<DrmBuffer> &buffer)
|
|
{
|
|
if (m_crtc->next()) {
|
|
return false;
|
|
}
|
|
if (!m_backend->session()->isActive()) {
|
|
m_crtc->setNext(buffer);
|
|
return false;
|
|
}
|
|
|
|
// Do we need to set a new mode first?
|
|
if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer.get())) {
|
|
if (!setModeLegacy(buffer.get())) {
|
|
return false;
|
|
}
|
|
}
|
|
const bool ok = drmModePageFlip(m_gpu->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0;
|
|
if (ok) {
|
|
m_crtc->setNext(buffer);
|
|
m_pageFlipPending = true;
|
|
} else {
|
|
qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
bool DrmOutput::setModeLegacy(DrmBuffer *buffer)
|
|
{
|
|
uint32_t connId = m_conn->id();
|
|
if (drmModeSetCrtc(m_gpu->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) {
|
|
return true;
|
|
} else {
|
|
qCWarning(KWIN_DRM) << "Mode setting failed";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool DrmOutput::doAtomicCommit(AtomicCommitMode mode)
|
|
{
|
|
drmModeAtomicReq *req = drmModeAtomicAlloc();
|
|
|
|
auto errorHandler = [this, mode, req] () {
|
|
if (mode == AtomicCommitMode::Test) {
|
|
// TODO: when we later test overlay planes, make sure we change only the right stuff back
|
|
}
|
|
if (req) {
|
|
drmModeAtomicFree(req);
|
|
}
|
|
|
|
if (dpmsMode() != m_dpmsModePending) {
|
|
qCWarning(KWIN_DRM) << "Setting DPMS failed";
|
|
m_dpmsModePending = dpmsMode();
|
|
if (dpmsMode() != DpmsMode::On) {
|
|
dpmsFinishOff();
|
|
}
|
|
}
|
|
|
|
// TODO: see above, rework later for overlay planes!
|
|
for (DrmPlane *p : qAsConst(m_nextPlanesFlipList)) {
|
|
p->setNext(nullptr);
|
|
}
|
|
m_nextPlanesFlipList.clear();
|
|
|
|
};
|
|
|
|
if (!req) {
|
|
qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request";
|
|
errorHandler();
|
|
return false;
|
|
}
|
|
|
|
uint32_t flags = 0;
|
|
// Do we need to set a new mode?
|
|
if (m_modesetRequested) {
|
|
if (m_dpmsModePending == DpmsMode::On) {
|
|
if (drmModeCreatePropertyBlob(m_gpu->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) {
|
|
qCWarning(KWIN_DRM) << "Failed to create property blob";
|
|
errorHandler();
|
|
return false;
|
|
}
|
|
}
|
|
if (!atomicReqModesetPopulate(req, m_dpmsModePending == DpmsMode::On)){
|
|
qCWarning(KWIN_DRM) << "Failed to populate Atomic Modeset";
|
|
errorHandler();
|
|
return false;
|
|
}
|
|
flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
|
|
}
|
|
|
|
if (!m_crtc->atomicPopulate(req)) {
|
|
qCWarning(KWIN_DRM) << "Failed to populate crtc. Abort atomic commit!";
|
|
errorHandler();
|
|
return false;
|
|
}
|
|
|
|
if (mode == AtomicCommitMode::Real) {
|
|
if (m_dpmsModePending == DpmsMode::On) {
|
|
if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) {
|
|
// TODO: Evaluating this condition should only be necessary, as long as we expect older kernels than 4.10.
|
|
flags |= DRM_MODE_ATOMIC_NONBLOCK;
|
|
}
|
|
|
|
#if HAVE_EGL_STREAMS
|
|
if (!m_gpu->useEglStreams())
|
|
// EglStreamBackend uses the NV_output_drm_flip_event EGL extension
|
|
// to register the flip event through eglStreamConsumerAcquireAttribNV
|
|
#endif
|
|
flags |= DRM_MODE_PAGE_FLIP_EVENT;
|
|
}
|
|
} else {
|
|
flags |= DRM_MODE_ATOMIC_TEST_ONLY;
|
|
}
|
|
|
|
bool ret = true;
|
|
// TODO: Make sure when we use more than one plane at a time, that we go through this list in the right order.
|
|
for (int i = m_nextPlanesFlipList.size() - 1; 0 <= i; i-- ) {
|
|
DrmPlane *p = m_nextPlanesFlipList[i];
|
|
ret &= p->atomicPopulate(req);
|
|
}
|
|
|
|
if (!ret) {
|
|
qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!";
|
|
errorHandler();
|
|
return false;
|
|
}
|
|
|
|
if (drmModeAtomicCommit(m_gpu->fd(), req, flags, this)) {
|
|
qCDebug(KWIN_DRM) << "Atomic request failed to commit: " << strerror(errno);
|
|
errorHandler();
|
|
return false;
|
|
}
|
|
|
|
if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) {
|
|
qCDebug(KWIN_DRM) << "Atomic Modeset successful.";
|
|
m_modesetRequested = false;
|
|
setDpmsModeInternal(m_dpmsModePending);
|
|
}
|
|
|
|
drmModeAtomicFree(req);
|
|
return true;
|
|
}
|
|
|
|
bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable)
|
|
{
|
|
if (enable) {
|
|
const QSize mSize = modeSize();
|
|
const QSize bufferSize = m_primaryPlane->next() ? m_primaryPlane->next()->size() : pixelSize();
|
|
const QSize sourceSize = hardwareTransforms() ? bufferSize : mSize;
|
|
QRect targetRect = QRect(QPoint(0, 0), mSize);
|
|
if (mSize != sourceSize) {
|
|
targetRect.setSize(sourceSize.scaled(mSize, Qt::AspectRatioMode::KeepAspectRatio));
|
|
targetRect.setX((mSize.width() - targetRect.width()) / 2);
|
|
targetRect.setY((mSize.height() - targetRect.height()) / 2);
|
|
}
|
|
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcX, 0);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcY, 0);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcW, sourceSize.width() << 16);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcH, sourceSize.height() << 16);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcX, targetRect.x());
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcY, targetRect.y());
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcW, targetRect.width());
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcH, targetRect.height());
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcId, m_crtc->id());
|
|
} else {
|
|
m_primaryPlane->setCurrent(nullptr);
|
|
m_primaryPlane->setNext(nullptr);
|
|
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcX, 0);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcY, 0);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcW, 0);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcH, 0);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcX, 0);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcY, 0);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcW, 0);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcH, 0);
|
|
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcId, 0);
|
|
}
|
|
m_conn->setValue(DrmConnector::PropertyIndex::CrtcId, enable ? m_crtc->id() : 0);
|
|
m_crtc->setValue(DrmCrtc::PropertyIndex::ModeId, enable ? m_blobId : 0);
|
|
m_crtc->setValue(DrmCrtc::PropertyIndex::Active, enable);
|
|
|
|
return m_conn->atomicPopulate(req);
|
|
}
|
|
|
|
int DrmOutput::gammaRampSize() const
|
|
{
|
|
return m_crtc->gammaRampSize();
|
|
}
|
|
|
|
bool DrmOutput::setGammaRamp(const GammaRamp &gamma)
|
|
{
|
|
return m_crtc->setGammaRamp(gamma);
|
|
}
|
|
|
|
void DrmOutput::setOverscan(uint32_t overscan)
|
|
{
|
|
if (m_conn->hasOverscan() && overscan <= 100) {
|
|
m_conn->setOverscan(overscan);
|
|
setOverscanInternal(overscan);
|
|
}
|
|
}
|
|
|
|
void DrmOutput::setVrr(bool enable)
|
|
{
|
|
if (!m_conn->vrrCapable() || m_crtc->isVrrEnabled() == enable) {
|
|
return;
|
|
}
|
|
if (!m_crtc->setVrr(enable) || (m_gpu->atomicModeSetting() && !doAtomicCommit(AtomicCommitMode::Test))) {
|
|
qCWarning(KWIN_DRM) << "Failed to set vrr on" << this;
|
|
setVrrPolicy(RenderLoop::VrrPolicy::Never);
|
|
m_crtc->setVrr(false);
|
|
}
|
|
}
|
|
|
|
bool DrmOutput::isCursorVisible()
|
|
{
|
|
return m_cursor[m_cursorIndex] && QRect(m_cursorPos, m_cursor[m_cursorIndex]->size()).intersects(QRect(0, 0, m_mode.vdisplay, m_mode.hdisplay));
|
|
}
|
|
|
|
}
|