File indexing completed on 2024-05-05 17:35:38

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <QSize>
0011 #include <QtTest>
0012 
0013 #include "mock_drm.h"
0014 
0015 #include "drm_backend.h"
0016 #include "drm_dumb_buffer.h"
0017 #include "drm_egl_backend.h"
0018 #include "drm_gpu.h"
0019 #include "drm_connector.h"
0020 #include "drm_crtc.h"
0021 #include "drm_plane.h"
0022 #include "drm_output.h"
0023 #include "drm_pipeline.h"
0024 #include "drm_pointer.h"
0025 #include "qpainterbackend.h"
0026 #include "core/session.h"
0027 
0028 #include <drm_fourcc.h>
0029 
0030 using namespace KWin;
0031 
0032 class DrmTest : public QObject
0033 {
0034     Q_OBJECT
0035 private Q_SLOTS:
0036     void testAmsDetection();
0037     void testOutputDetection();
0038     void testZeroModesHandling();
0039     void testModeGeneration_data();
0040     void testModeGeneration();
0041     void testConnectorLifetime();
0042 };
0043 
0044 static void verifyCleanup(MockGpu *mockGpu)
0045 {
0046     QVERIFY(mockGpu->drmConnectors.isEmpty());
0047     QVERIFY(mockGpu->drmEncoders.isEmpty());
0048     QVERIFY(mockGpu->drmCrtcs.isEmpty());
0049     QVERIFY(mockGpu->drmPlanes.isEmpty());
0050     QVERIFY(mockGpu->drmPlaneRes.isEmpty());
0051     QVERIFY(mockGpu->fbs.isEmpty());
0052     QVERIFY(mockGpu->drmProps.isEmpty());
0053     QVERIFY(mockGpu->drmObjectProperties.isEmpty());
0054     QVERIFY(mockGpu->drmPropertyBlobs.isEmpty());
0055 }
0056 
0057 void DrmTest::testAmsDetection()
0058 {
0059     const auto mockGpu = std::make_unique<MockGpu>(1, 0);
0060 
0061     const auto session = Session::create(Session::Type::Noop);
0062     const auto backend = std::make_unique<DrmBackend>(session.get());
0063 
0064     // gpu without planes should use legacy mode
0065     auto gpu = std::make_unique<DrmGpu>(backend.get(), "legacy", 1, 0);
0066     QVERIFY(!gpu->atomicModeSetting());
0067 
0068     // gpu with planes should use AMS
0069     mockGpu->planes << std::make_shared<MockPlane>(mockGpu.get(), PlaneType::Primary, 0);
0070     gpu = std::make_unique<DrmGpu>(backend.get(), "AMS", 1, 0);
0071     QVERIFY(gpu->atomicModeSetting());
0072 
0073     // but not if the kernel doesn't allow it
0074     mockGpu->deviceCaps[MOCKDRM_DEVICE_CAP_ATOMIC] = 0;
0075     gpu = std::make_unique<DrmGpu>(backend.get(), "legacy 2", 1, 0);
0076     QVERIFY(!gpu->atomicModeSetting());
0077 
0078     gpu.reset();
0079     verifyCleanup(mockGpu.get());
0080 }
0081 
0082 void DrmTest::testOutputDetection()
0083 {
0084     const auto mockGpu = std::make_unique<MockGpu>(1, 5);
0085 
0086     const auto one = std::make_shared<MockConnector>(mockGpu.get());
0087     const auto two = std::make_shared<MockConnector>(mockGpu.get());
0088     const auto vr = std::make_shared<MockConnector>(mockGpu.get(), true);
0089     mockGpu->connectors.push_back(one);
0090     mockGpu->connectors.push_back(two);
0091     mockGpu->connectors.push_back(vr);
0092 
0093     const auto session = Session::create(Session::Type::Noop);
0094     const auto backend = std::make_unique<DrmBackend>(session.get());
0095     const auto renderBackend = backend->createQPainterBackend();
0096     auto gpu = std::make_unique<DrmGpu>(backend.get(), "test", 1, 0);
0097     QVERIFY(gpu->updateOutputs());
0098 
0099     // 3 outputs should be detected, one of them non-desktop
0100     const auto outputs = gpu->drmOutputs();
0101     QCOMPARE(outputs.size(), 3);
0102     const auto vrOutput = std::find_if(outputs.begin(), outputs.end(), [](const auto &output) {
0103         return output->isNonDesktop();
0104     });
0105     QVERIFY(vrOutput != outputs.end());
0106     QVERIFY(static_cast<DrmOutput *>(*vrOutput)->connector()->id() == vr->id);
0107 
0108     // test hotunplugging
0109     mockGpu->connectors.removeOne(one);
0110     QVERIFY(gpu->updateOutputs());
0111     QCOMPARE(gpu->drmOutputs().size(), 2);
0112 
0113     // test hotplugging
0114     mockGpu->connectors.push_back(one);
0115     QVERIFY(gpu->updateOutputs());
0116     QCOMPARE(gpu->drmOutputs().size(), 3);
0117 
0118     // connector state changing to disconnected should count as a hotunplug
0119     one->connection = DRM_MODE_DISCONNECTED;
0120     QVERIFY(gpu->updateOutputs());
0121     QCOMPARE(gpu->drmOutputs().size(), 2);
0122 
0123     // don't crash if all connectors are disconnected
0124     two->connection = DRM_MODE_DISCONNECTED;
0125     vr->connection = DRM_MODE_DISCONNECTED;
0126     QVERIFY(gpu->updateOutputs());
0127     QVERIFY(gpu->drmOutputs().empty());
0128 
0129     gpu.reset();
0130     verifyCleanup(mockGpu.get());
0131 }
0132 
0133 void DrmTest::testZeroModesHandling()
0134 {
0135     const auto mockGpu = std::make_unique<MockGpu>(1, 5);
0136 
0137     const auto conn = std::make_shared<MockConnector>(mockGpu.get());
0138     mockGpu->connectors.push_back(conn);
0139 
0140     const auto session = Session::create(Session::Type::Noop);
0141     const auto backend = std::make_unique<DrmBackend>(session.get());
0142     const auto renderBackend = backend->createQPainterBackend();
0143     auto gpu = std::make_unique<DrmGpu>(backend.get(), "test", 1, 0);
0144 
0145     // connector with zero modes should be ignored
0146     conn->modes.clear();
0147     QVERIFY(gpu->updateOutputs());
0148     QVERIFY(gpu->drmOutputs().empty());
0149 
0150     // once it has modes, it should be detected
0151     conn->addMode(1920, 1080, 60);
0152     QVERIFY(gpu->updateOutputs());
0153     QCOMPARE(gpu->drmOutputs().size(), 1);
0154 
0155     // if an update says it has no modes anymore but it's still connected, ignore that
0156     conn->modes.clear();
0157     QVERIFY(gpu->updateOutputs());
0158     QCOMPARE(gpu->drmOutputs().size(), 1);
0159     QVERIFY(!gpu->drmOutputs().constFirst()->modes().empty());
0160 
0161     gpu.reset();
0162     verifyCleanup(mockGpu.get());
0163 }
0164 
0165 void DrmTest::testModeGeneration_data()
0166 {
0167     QTest::addColumn<QSize>("nativeMode");
0168     QTest::addColumn<QVector<QSize>>("expectedModes");
0169 
0170     QTest::newRow("2160p") << QSize(3840, 2160) << QVector<QSize>{
0171         QSize(1600, 1200),
0172         QSize(1280, 1024),
0173         QSize(1024, 768),
0174         QSize(2560, 1600),
0175         QSize(1920, 1200),
0176         QSize(1280, 800),
0177         QSize(3840, 2160),
0178         QSize(3200, 1800),
0179         QSize(2880, 1620),
0180         QSize(2560, 1440),
0181         QSize(1920, 1080),
0182         QSize(1600, 900),
0183         QSize(1368, 768),
0184         QSize(1280, 720),
0185     };
0186     QTest::newRow("1440p") << QSize(2560, 1440) << QVector<QSize>{
0187         QSize(1600, 1200),
0188         QSize(1280, 1024),
0189         QSize(1024, 768),
0190         QSize(1920, 1200),
0191         QSize(1280, 800),
0192         QSize(2560, 1440),
0193         QSize(1920, 1080),
0194         QSize(1600, 900),
0195         QSize(1368, 768),
0196         QSize(1280, 720),
0197     };
0198     QTest::newRow("1080p") << QSize(1920, 1080) << QVector<QSize>{
0199         QSize(1280, 1024),
0200         QSize(1024, 768),
0201         QSize(1280, 800),
0202         QSize(1920, 1080),
0203         QSize(1600, 900),
0204         QSize(1368, 768),
0205         QSize(1280, 720),
0206     };
0207 
0208     QTest::newRow("2160p 21:9") << QSize(5120, 2160) << QVector<QSize>{
0209         QSize(5120, 2160),
0210         QSize(1600, 1200),
0211         QSize(1280, 1024),
0212         QSize(1024, 768),
0213         QSize(2560, 1600),
0214         QSize(1920, 1200),
0215         QSize(1280, 800),
0216         QSize(3840, 2160),
0217         QSize(3200, 1800),
0218         QSize(2880, 1620),
0219         QSize(2560, 1440),
0220         QSize(1920, 1080),
0221         QSize(1600, 900),
0222         QSize(1368, 768),
0223         QSize(1280, 720),
0224     };
0225     QTest::newRow("1440p 21:9") << QSize(3440, 1440) << QVector<QSize>{
0226         QSize(3440, 1440),
0227         QSize(1600, 1200),
0228         QSize(1280, 1024),
0229         QSize(1024, 768),
0230         QSize(1920, 1200),
0231         QSize(1280, 800),
0232         QSize(2560, 1440),
0233         QSize(1920, 1080),
0234         QSize(1600, 900),
0235         QSize(1368, 768),
0236         QSize(1280, 720),
0237     };
0238     QTest::newRow("1080p 21:9") << QSize(2560, 1080) << QVector<QSize>{
0239         QSize(2560, 1080),
0240         QSize(1280, 1024),
0241         QSize(1024, 768),
0242         QSize(1280, 800),
0243         QSize(1920, 1080),
0244         QSize(1600, 900),
0245         QSize(1368, 768),
0246         QSize(1280, 720),
0247     };
0248 }
0249 
0250 void DrmTest::testModeGeneration()
0251 {
0252     const auto mockGpu = std::make_unique<MockGpu>(1, 5);
0253 
0254     const auto conn = std::make_shared<MockConnector>(mockGpu.get());
0255     mockGpu->connectors.push_back(conn);
0256 
0257     const auto session = Session::create(Session::Type::Noop);
0258     const auto backend = std::make_unique<DrmBackend>(session.get());
0259     const auto renderBackend = backend->createQPainterBackend();
0260     auto gpu = std::make_unique<DrmGpu>(backend.get(), "test", 1, 0);
0261 
0262     QFETCH(QSize, nativeMode);
0263     QFETCH(QVector<QSize>, expectedModes);
0264 
0265     conn->modes.clear();
0266     conn->addMode(nativeMode.width(), nativeMode.height(), 60);
0267     QVERIFY(gpu->updateOutputs());
0268     QCOMPARE(gpu->drmOutputs().size(), 1);
0269     // no mode generation without the scaling property
0270     QCOMPARE(gpu->drmOutputs().front()->modes().size(), 1);
0271 
0272     mockGpu->connectors.removeAll(conn);
0273     QVERIFY(gpu->updateOutputs());
0274 
0275     conn->props.push_back(MockProperty(conn.get(), QStringLiteral("scaling mode"), 0, 0, QVector<QByteArray>{"None", "Full", "Center", "Full aspect"}));
0276     mockGpu->connectors.push_back(conn);
0277     QVERIFY(gpu->updateOutputs());
0278 
0279     DrmOutput *const output = gpu->drmOutputs().front();
0280     QCOMPARE(output->modes().size(), expectedModes.size());
0281     for (const auto &mode : output->modes()) {
0282         QVERIFY(expectedModes.contains(mode->size()));
0283         QVERIFY(mode->size().width() <= nativeMode.width());
0284         QVERIFY(mode->size().height() <= nativeMode.height());
0285         QVERIFY(mode->refreshRate() <= 60000);
0286     }
0287 
0288     gpu.reset();
0289     verifyCleanup(mockGpu.get());
0290 }
0291 
0292 void DrmTest::testConnectorLifetime()
0293 {
0294     // don't crash if output lifetime is extended beyond the connector
0295     const auto mockGpu = std::make_unique<MockGpu>(1, 5);
0296 
0297     const auto conn = std::make_shared<MockConnector>(mockGpu.get());
0298     mockGpu->connectors.push_back(conn);
0299 
0300     const auto session = Session::create(Session::Type::Noop);
0301     const auto backend = std::make_unique<DrmBackend>(session.get());
0302     const auto renderBackend = backend->createQPainterBackend();
0303     auto gpu = std::make_unique<DrmGpu>(backend.get(), "test", 1, 0);
0304 
0305     QVERIFY(gpu->updateOutputs());
0306     QCOMPARE(gpu->drmOutputs().size(), 1);
0307 
0308     DrmOutput *const output = gpu->drmOutputs().front();
0309 
0310     output->ref();
0311     mockGpu->connectors.clear();
0312     QVERIFY(gpu->updateOutputs());
0313     output->unref();
0314 
0315     gpu.reset();
0316     verifyCleanup(mockGpu.get());
0317 }
0318 
0319 QTEST_GUILESS_MAIN(DrmTest)
0320 #include "drmTest.moc"