drm_output.cpp 26.7 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1
2
3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5
    SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
6

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
7
8
    SPDX-License-Identifier: GPL-2.0-or-later
*/
9
10
#include "drm_output.h"
#include "drm_backend.h"
11
12
13
#include "drm_object_crtc.h"
#include "drm_object_connector.h"

14
#include "composite.h"
15
#include "cursor.h"
16
#include "logging.h"
17
#include "main.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
18
#include "renderloop.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
19
20
#include "screens.h"
#include "session.h"
21
22
#include "wayland_server.h"
// KWayland
23
#include <KWaylandServer/output_interface.h>
24
// Qt
25
#include <QMatrix4x4>
26
#include <QCryptographicHash>
27
#include <QPainter>
28
29
// c++
#include <cerrno>
30
31
32
33
// drm
#include <xf86drm.h>
#include <libdrm/drm_mode.h>

34
35
#include "drm_gpu.h"

36
37
38
namespace KWin
{

39
DrmOutput::DrmOutput(DrmBackend *backend, DrmGpu *gpu)
40
    : AbstractWaylandOutput(backend)
41
    , m_backend(backend)
42
    , m_gpu(gpu)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
43
    , m_renderLoop(new RenderLoop(this))
44
45
46
47
48
{
}

DrmOutput::~DrmOutput()
{
49
    Q_ASSERT(!m_pageFlipPending);
50
    teardown();
51
52
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
53
54
55
56
57
RenderLoop *DrmOutput::renderLoop() const
{
    return m_renderLoop;
}

58
59
void DrmOutput::teardown()
{
60
61
62
    if (m_deleted) {
        return;
    }
63
    m_deleted = true;
64
    hideCursor();
65
    m_crtc->blank(this);
66
67
68
69
70
71

    if (m_primaryPlane) {
        // TODO: when having multiple planes, also clean up these
        m_primaryPlane->setCurrent(nullptr);
    }

72
73
    m_cursor[0].reset(nullptr);
    m_cursor[1].reset(nullptr);
74
75
76
77
    if (!m_pageFlipPending) {
        deleteLater();
    } //else will be deleted in the page flip handler
    //this is needed so that the pageflipcallback handle isn't deleted
78
79
}

80
void DrmOutput::releaseGbm()
81
{
Xaver Hugl's avatar
Xaver Hugl committed
82
    if (const auto &b = m_crtc->current()) {
83
        b->releaseGbm();
84
    }
85
86
    if (m_primaryPlane && m_primaryPlane->current()) {
        m_primaryPlane->current()->releaseGbm();
87
88
89
    }
}

90
bool DrmOutput::hideCursor()
91
{
92
    return drmModeSetCursor(m_gpu->fd(), m_crtc->id(), 0, 0, 0) == 0;
93
94
}

95
bool DrmOutput::showCursor(DrmDumbBuffer *c)
96
97
{
    const QSize &s = c->size();
98
    return drmModeSetCursor(m_gpu->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0;
99
100
}

101
bool DrmOutput::showCursor()
102
{
103
104
105
106
    if (m_deleted) {
        return false;
    }

107
    const bool ret = showCursor(m_cursor[m_cursorIndex].data());
108
    if (!ret) {
109
        qCDebug(KWIN_DRM) << "DrmOutput::showCursor(DrmDumbBuffer) failed";
110
111
112
        return ret;
    }

113
114
115
116
    if (m_hasNewCursor) {
        m_cursorIndex = (m_cursorIndex + 1) % 2;
        m_hasNewCursor = false;
    }
117
118

    return ret;
119
120
}

121
122
123
124
125
126
127
128
129
130
131
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()
132
{
133
    if (m_deleted) {
134
        return false;
135
    }
136
137
    const Cursor *cursor = Cursors::self()->currentCursor();
    const QImage cursorImage = cursor->image();
138
    if (cursorImage.isNull()) {
139
        return false;
140
    }
141

142
    QImage *c = m_cursor[m_cursorIndex]->image();
143
144
145
146
147
148
149
150
    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;
151
    c->fill(Qt::transparent);
David Edmundson's avatar
David Edmundson committed
152

153
154
    QPainter p;
    p.begin(c);
155
    p.setWorldTransform(logicalToNativeMatrix(cursor->rect(), 1, transform()).toTransform());
156
157
    p.drawImage(QPoint(0, 0), cursorImage);
    p.end();
158
159

    return true;
160
161
}

162
void DrmOutput::moveCursor()
163
{
164
    Cursor *cursor = Cursors::self()->currentCursor();
165
    const QMatrix4x4 hotspotMatrix = logicalToNativeMatrix(cursor->rect(), scale(), transform());
166
    const QMatrix4x4 monitorMatrix = logicalToNativeMatrix(geometry(), scale(), transform());
167

168
    QPoint pos = monitorMatrix.map(cursor->pos());
169
    pos -= hotspotMatrix.map(cursor->hotspot());
170

171
    drmModeMoveCursor(m_gpu->fd(), m_crtc->id(), pos.x(), pos.y());
172
173
}

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
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;
}
}
192

193
bool DrmOutput::init(drmModeConnector *connector)
194
195
{
    initUuid();
196
197
    if (m_gpu->atomicModeSetting() && !m_primaryPlane) {
        return false;
198
    }
199

200
    setInternal(m_conn->isInternal());
Roman Gilg's avatar
Roman Gilg committed
201
    setDpmsSupported(true);
202
    initOutputDevice(connector);
Roman Gilg's avatar
Roman Gilg committed
203

204
    if (!m_gpu->atomicModeSetting() && !m_crtc->blank(this)) {
Roman Gilg's avatar
Roman Gilg committed
205
206
207
208
        // We use legacy mode and the initial output blank failed.
        return false;
    }

209
    updateDpms(KWaylandServer::OutputInterface::DpmsMode::On);
210
211
212
213
214
215
216
    return true;
}

void DrmOutput::initUuid()
{
    QCryptographicHash hash(QCryptographicHash::Md5);
    hash.addData(QByteArray::number(m_conn->id()));
217
218
219
    hash.addData(m_conn->edid()->eisaId());
    hash.addData(m_conn->edid()->monitorName());
    hash.addData(m_conn->edid()->serialNumber());
220
221
222
223
224
    m_uuid = hash.result().toHex().left(10);
}

void DrmOutput::initOutputDevice(drmModeConnector *connector)
{
225
    // read in mode information
226
    QVector<KWaylandServer::OutputDeviceInterface::Mode> modes;
227
    for (int i = 0; i < connector->count_modes; ++i) {
228
229
        // 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
230
        auto *m = &connector->modes[i];
231
        KWaylandServer::OutputDeviceInterface::ModeFlags deviceflags;
232
        if (isCurrentMode(m)) {
233
            deviceflags |= KWaylandServer::OutputDeviceInterface::ModeFlag::Current;
234
235
        }
        if (m->type & DRM_MODE_TYPE_PREFERRED) {
236
            deviceflags |= KWaylandServer::OutputDeviceInterface::ModeFlag::Preferred;
237
238
        }

239
        KWaylandServer::OutputDeviceInterface::Mode mode;
240
241
242
        mode.id = i;
        mode.size = QSize(m->hdisplay, m->vdisplay);
        mode.flags = deviceflags;
243
244
        mode.refreshRate = refreshRateForMode(m);
        modes << mode;
245
    }
246

247
248
    setName(m_conn->connectorName());
    initInterfaces(m_conn->modelName(), m_conn->edid()->manufacturerString(), m_uuid, m_conn->physicalSize(), modes, m_conn->edid()->raw());
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
}

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;
}

270
271
272
bool DrmOutput::initCursor(const QSize &cursorSize)
{
    auto createCursor = [this, cursorSize] (int index) {
273
        m_cursor[index].reset(new DrmDumbBuffer(m_gpu, cursorSize));
274
275
276
277
278
279
280
281
282
283
284
        if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) {
            return false;
        }
        return true;
    };
    if (!createCursor(0) || !createCursor(1)) {
        return false;
    }
    return true;
}

285
286
287
288
void DrmOutput::updateEnablement(bool enable)
{
    if (enable) {
        m_dpmsModePending = DpmsMode::On;
289
        if (m_gpu->atomicModeSetting()) {
290
291
292
293
294
295
296
297
298
            atomicEnable();
        } else {
            if (dpmsLegacyApply()) {
                m_backend->enableOutput(this, true);
            }
        }

    } else {
        m_dpmsModePending = DpmsMode::Off;
299
        if (m_gpu->atomicModeSetting()) {
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
            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);
318
    dpmsFinishOn();
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335

    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();
    }
}

336
static DrmOutput::DpmsMode fromWaylandDpmsMode(KWaylandServer::OutputInterface::DpmsMode wlMode)
337
{
338
    using namespace KWaylandServer;
339
340
341
342
343
344
345
346
347
348
349
350
351
352
    switch (wlMode) {
    case OutputInterface::DpmsMode::On:
        return DrmOutput::DpmsMode::On;
    case OutputInterface::DpmsMode::Standby:
        return DrmOutput::DpmsMode::Standby;
    case OutputInterface::DpmsMode::Suspend:
        return DrmOutput::DpmsMode::Suspend;
    case OutputInterface::DpmsMode::Off:
        return DrmOutput::DpmsMode::Off;
    default:
        Q_UNREACHABLE();
    }
}

353
static KWaylandServer::OutputInterface::DpmsMode toWaylandDpmsMode(DrmOutput::DpmsMode mode)
354
{
355
    using namespace KWaylandServer;
356
357
358
359
360
361
362
363
364
365
366
367
368
369
    switch (mode) {
    case DrmOutput::DpmsMode::On:
        return OutputInterface::DpmsMode::On;
    case DrmOutput::DpmsMode::Standby:
        return OutputInterface::DpmsMode::Standby;
    case DrmOutput::DpmsMode::Suspend:
        return OutputInterface::DpmsMode::Suspend;
    case DrmOutput::DpmsMode::Off:
        return OutputInterface::DpmsMode::Off;
    default:
        Q_UNREACHABLE();
    }
}

370
void DrmOutput::updateDpms(KWaylandServer::OutputInterface::DpmsMode mode)
371
{
372
    if (!m_conn->dpms() || !isEnabled()) {
373
374
        return;
    }
Roman Gilg's avatar
Roman Gilg committed
375
376

    const auto drmMode = fromWaylandDpmsMode(mode);
377

Roman Gilg's avatar
Roman Gilg committed
378
    if (drmMode == m_dpmsModePending) {
379
        qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged.";
380
        waylandOutput()->setDpmsMode(mode);
381
382
        return;
    }
383

Roman Gilg's avatar
Roman Gilg committed
384
    m_dpmsModePending = drmMode;
385

386
    if (m_gpu->atomicModeSetting()) {
387
        m_modesetRequested = true;
Roman Gilg's avatar
Roman Gilg committed
388
        if (drmMode == DpmsMode::On) {
389
390
391
            if (m_atomicOffPending) {
                Q_ASSERT(m_pageFlipPending);
                m_atomicOffPending = false;
392
            }
393
            dpmsFinishOn();
394
        } else {
395
            m_atomicOffPending = true;
396
397
398
            if (!m_pageFlipPending) {
                dpmsAtomicOff();
            }
399
400
        }
    } else {
401
       dpmsLegacyApply();
402
    }
403
404
}

405
void DrmOutput::dpmsFinishOn()
406
407
{
    qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On.";
408

409
    waylandOutput()->setDpmsMode(toWaylandDpmsMode(DpmsMode::On));
410
411

    m_backend->checkOutputsAreOn();
412
    m_crtc->blank(this);
413
    m_renderLoop->uninhibit();
414
415
416
417
418
    if (Compositor *compositor = Compositor::self()) {
        compositor->addRepaintFull();
    }
}

419
void DrmOutput::dpmsFinishOff()
420
421
422
{
    qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off.";

423
424
425
    if (isEnabled()) {
        waylandOutput()->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending));
        m_backend->createDpmsFilter();
426
427
    } else {
        waylandOutput()->setDpmsMode(toWaylandDpmsMode(DpmsMode::Off));
428
    }
429
    m_renderLoop->inhibit();
430
}
431

432
433
bool DrmOutput::dpmsLegacyApply()
{
434
    if (drmModeConnectorSetProperty(m_gpu->fd(), m_conn->id(),
435
                                    m_conn->dpms()->propId(), uint64_t(m_dpmsModePending)) < 0) {
436
437
438
439
440
441
442
443
444
445
446
        m_dpmsModePending = m_dpmsMode;
        qCWarning(KWIN_DRM) << "Setting DPMS failed";
        return false;
    }
    if (m_dpmsModePending == DpmsMode::On) {
        dpmsFinishOn();
    } else {
        dpmsFinishOff();
    }
    m_dpmsMode = m_dpmsModePending;
    return true;
447
448
}

449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
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;
478
    }
479
480
    return m_primaryPlane->transformation() == outputToPlaneTransform(transform());
}
481

482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
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);
        }
502
503
    }
    m_modesetRequested = true;
504

505
    // show cursor only if is enabled, i.e if pointer device is presentP
506
    if (!m_backend->isCursorHidden() && !m_backend->usesSoftwareCursor()) {
507
508
509
510
        // the cursor might need to get rotated
        updateCursor();
        showCursor();
    }
511
512
}

Xaver Hugl's avatar
Xaver Hugl committed
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
void DrmOutput::updateMode(uint32_t width, uint32_t height, uint32_t refreshRate)
{
    if (m_mode.hdisplay == width && m_mode.vdisplay == height && m_mode.vrefresh == 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 && mode.vrefresh == 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, uuid().constData());
}

531
532
533
void DrmOutput::updateMode(int modeIndex)
{
    // get all modes on the connector
534
    DrmScopedPointer<drmModeConnector> connector(drmModeGetConnector(m_gpu->fd(), m_conn->id()));
535
536
537
538
539
540
541
542
543
544
    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;
Roman Gilg's avatar
Roman Gilg committed
545
546
547
548
549
    setWaylandMode();
}

void DrmOutput::setWaylandMode()
{
550
551
    AbstractWaylandOutput::setWaylandMode(QSize(m_mode.hdisplay, m_mode.vdisplay),
                                          refreshRateForMode(&m_mode));
552
553
}

554
555
void DrmOutput::pageFlipped()
{
Roman Gilg's avatar
Roman Gilg committed
556
    // In legacy mode we might get a page flip through a blank.
557
    Q_ASSERT(m_pageFlipPending || !m_gpu->atomicModeSetting());
558
    m_pageFlipPending = false;
Roman Gilg's avatar
Roman Gilg committed
559

560
561
562
563
    if (m_deleted) {
        deleteLater();
        return;
    }
564

565
566
567
    if (!m_crtc) {
        return;
    }
Xaver Hugl's avatar
Xaver Hugl committed
568
569
570
571
    if (m_gpu->atomicModeSetting()) {
        if (!m_primaryPlane->next()) {
            if (m_primaryPlane->current()) {
                m_primaryPlane->current()->releaseGbm();
572
            }
Xaver Hugl's avatar
Xaver Hugl committed
573
574
575
576
            return;
        }
        for (DrmPlane *p : m_nextPlanesFlipList) {
            p->flipBuffer();
577
        }
Xaver Hugl's avatar
Xaver Hugl committed
578
        m_nextPlanesFlipList.clear();
579
    } else {
Xaver Hugl's avatar
Xaver Hugl committed
580
581
        if (!m_crtc->next()) {
            // on manual vt switch
Xaver Hugl's avatar
Xaver Hugl committed
582
            if (const auto &b = m_crtc->current()) {
Xaver Hugl's avatar
Xaver Hugl committed
583
                b->releaseGbm();
584
            }
585
        }
586
        m_crtc->flipBuffer();
587
    }
588
589
590
591

    if (m_atomicOffPending) {
        dpmsAtomicOff();
    }
592
593
}

Xaver Hugl's avatar
Xaver Hugl committed
594
bool DrmOutput::present(const QSharedPointer<DrmBuffer> &buffer)
595
{
Xaver Hugl's avatar
Xaver Hugl committed
596
597
598
    if (!buffer || buffer->bufferId() == 0) {
        return false;
    }
599
600
601
    if (m_dpmsModePending != DpmsMode::On) {
        return false;
    }
Xaver Hugl's avatar
Xaver Hugl committed
602
    return m_gpu->atomicModeSetting() ? presentAtomically(buffer) : presentLegacy(buffer);
603
604
}

605
bool DrmOutput::dpmsAtomicOff()
606
{
607
    m_atomicOffPending = false;
608
609
610
611
612
613
614

    // TODO: With multiple planes: deactivate all of them here
    m_primaryPlane->setNext(nullptr);
    m_nextPlanesFlipList << m_primaryPlane;

    if (!doAtomicCommit(AtomicCommitMode::Test)) {
        qCDebug(KWIN_DRM) << "Atomic test commit to Dpms Off failed. Aborting.";
615
616
        return false;
    }
617
618
    if (!doAtomicCommit(AtomicCommitMode::Real)) {
        qCDebug(KWIN_DRM) << "Atomic commit to Dpms Off failed. This should have never happened! Aborting.";
619
620
        return false;
    }
621
    m_nextPlanesFlipList.clear();
622
    dpmsFinishOff();
623

624
625
    return true;
}
626

Xaver Hugl's avatar
Xaver Hugl committed
627
bool DrmOutput::presentAtomically(const QSharedPointer<DrmBuffer> &buffer)
628
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
629
630
    if (!m_backend->session()->isActive()) {
        qCWarning(KWIN_DRM) << "Refusing to present output because session is inactive";
631
        return false;
632
633
    }

634
635
636
    if (m_pageFlipPending) {
        qCWarning(KWIN_DRM) << "Page not yet flipped.";
        return false;
637
638
    }

639
#if HAVE_EGL_STREAMS
640
    if (m_gpu->useEglStreams() && !m_modesetRequested) {
641
642
643
644
645
646
        // EglStreamBackend queues normal page flips through EGL,
        // modesets are still performed through DRM-KMS
        m_pageFlipPending = true;
        return true;
    }
#endif
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
647

648
649
650
651
652
    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?
653
        //TODO: Probably should undo setNext and reset the flip list
654
        qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present.";
655
656
657
        // go back to previous state
        if (m_lastWorkingState.valid) {
            m_mode = m_lastWorkingState.mode;
Roman Gilg's avatar
Roman Gilg committed
658
            setTransform(m_lastWorkingState.transform);
659
660
661
662
663
            setGlobalPos(m_lastWorkingState.globalPos);
            if (m_primaryPlane) {
                m_primaryPlane->setTransformation(m_lastWorkingState.planeTransformations);
            }
            m_modesetRequested = true;
664
            if (!m_backend->isCursorHidden()) {
665
666
667
668
                // the cursor might need to get rotated
                updateCursor();
                showCursor();
            }
Roman Gilg's avatar
Roman Gilg committed
669
            setWaylandMode();
670
671
            emit screens()->changed();
        }
672
673
        return false;
    }
674
    const bool wasModeset = m_modesetRequested;
675
676
    if (!doAtomicCommit(AtomicCommitMode::Real)) {
        qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present.";
677
        //TODO: Probably should undo setNext and reset the flip list
678
        return false;
679
    }
680
681
682
    if (wasModeset) {
        // store current mode set as new good state
        m_lastWorkingState.mode = m_mode;
Roman Gilg's avatar
Roman Gilg committed
683
        m_lastWorkingState.transform = transform();
Roman Gilg's avatar
Roman Gilg committed
684
        m_lastWorkingState.globalPos = globalPos();
685
686
687
688
        if (m_primaryPlane) {
            m_lastWorkingState.planeTransformations = m_primaryPlane->transformation();
        }
        m_lastWorkingState.valid = true;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
689
        m_renderLoop->setRefreshRate(refreshRateForMode(&m_mode));
690
    }
691
    m_pageFlipPending = true;
692
693
694
    return true;
}

Xaver Hugl's avatar
Xaver Hugl committed
695
bool DrmOutput::presentLegacy(const QSharedPointer<DrmBuffer> &buffer)
696
{
697
    if (m_crtc->next()) {
698
699
        return false;
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
700
    if (!m_backend->session()->isActive()) {
701
        m_crtc->setNext(buffer);
702
703
704
705
        return false;
    }

    // Do we need to set a new mode first?
Xaver Hugl's avatar
Xaver Hugl committed
706
707
    if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer.get())) {
        if (!setModeLegacy(buffer.get())) {
708
            return false;
Roman Gilg's avatar
Roman Gilg committed
709
        }
710
    }
711
    const bool ok = drmModePageFlip(m_gpu->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0;
712
    if (ok) {
713
        m_crtc->setNext(buffer);
Xaver Hugl's avatar
Xaver Hugl committed
714
        m_pageFlipPending = true;
715
716
717
718
719
720
721
722
    } else {
        qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno);
    }
    return ok;
}

bool DrmOutput::setModeLegacy(DrmBuffer *buffer)
{
723
    uint32_t connId = m_conn->id();
724
    if (drmModeSetCrtc(m_gpu->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) {
725
726
727
728
729
730
731
        return true;
    } else {
        qCWarning(KWIN_DRM) << "Mode setting failed";
        return false;
    }
}

732
bool DrmOutput::doAtomicCommit(AtomicCommitMode mode)
733
{
734
735
736
737
738
739
740
741
742
743
744
745
746
747
    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 (m_dpmsMode != m_dpmsModePending) {
            qCWarning(KWIN_DRM) << "Setting DPMS failed";
            m_dpmsModePending = m_dpmsMode;
            if (m_dpmsMode != DpmsMode::On) {
748
                dpmsFinishOff();
749
750
751
752
753
754
755
756
757
758
759
760
            }
        }

        // TODO: see above, rework later for overlay planes!
        for (DrmPlane *p : m_nextPlanesFlipList) {
            p->setNext(nullptr);
        }
        m_nextPlanesFlipList.clear();

    };

    if (!req) {
761
762
763
        qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request";
        errorHandler();
        return false;
764
765
766
767
768
769
    }

    uint32_t flags = 0;
    // Do we need to set a new mode?
    if (m_modesetRequested) {
        if (m_dpmsModePending == DpmsMode::On) {
770
            if (drmModeCreatePropertyBlob(m_gpu->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) {
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
                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 (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;
            }
790
791

#if HAVE_EGL_STREAMS
792
            if (!m_gpu->useEglStreams())
793
794
795
796
                // EglStreamBackend uses the NV_output_drm_flip_event EGL extension
                // to register the flip event through eglStreamConsumerAcquireAttribNV
#endif
                flags |= DRM_MODE_PAGE_FLIP_EVENT;
797
        }
798
    } else {
799
        flags |= DRM_MODE_ATOMIC_TEST_ONLY;
800
801
802
    }

    bool ret = true;
803
804
805
806
807
    // 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);
    }
808

809
810
811
812
813
    if (!ret) {
        qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!";
        errorHandler();
        return false;
    }
814

815
816
    if (drmModeAtomicCommit(m_gpu->fd(), req, flags, this)) {
        qCDebug(KWIN_DRM) << "Atomic request failed to commit: " << strerror(errno);
817
818
819
        errorHandler();
        return false;
    }
820

821
822
823
824
    if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) {
        qCDebug(KWIN_DRM) << "Atomic Modeset successful.";
        m_modesetRequested = false;
        m_dpmsMode = m_dpmsModePending;
825
    }
826
827
828
829
830
831
832
833

    drmModeAtomicFree(req);
    return true;
}

bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable)
{
    if (enable) {
834
        const QSize mSize = modeSize();
Xaver Hugl's avatar
Xaver Hugl committed
835
836
837
838
839
840
841
842
        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);
        }
843

Xaver Hugl's avatar
Xaver Hugl committed
844
845
846
847
848
849
850
851
852
        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());
853
854
855
856
    } else {
        m_primaryPlane->setCurrent(nullptr);
        m_primaryPlane->setNext(nullptr);

Xaver Hugl's avatar
Xaver Hugl committed
857
858
859
860
861
862
863
864
865
866
867
868
869
        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);
870
871
872
873
874
875

    bool ret = true;
    ret &= m_conn->atomicPopulate(req);
    ret &= m_crtc->atomicPopulate(req);

    return ret;
876
}
877

878
int DrmOutput::gammaRampSize() const
879
{
880
    return m_crtc->gammaRampSize();
881
882
}

883
bool DrmOutput::setGammaRamp(const GammaRamp &gamma)
884
885
886
887
{
    return m_crtc->setGammaRamp(gamma);
}

888
}