File indexing completed on 2025-03-16 13:55:45
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"