File indexing completed on 2024-11-10 04:55:53
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 <QTest> 0012 0013 #include "mock_drm.h" 0014 0015 #include "core/outputlayer.h" 0016 #include "core/session.h" 0017 #include "drm_backend.h" 0018 #include "drm_connector.h" 0019 #include "drm_crtc.h" 0020 #include "drm_egl_backend.h" 0021 #include "drm_gpu.h" 0022 #include "drm_output.h" 0023 #include "drm_pipeline.h" 0024 #include "drm_plane.h" 0025 #include "drm_pointer.h" 0026 #include "platformsupport/scenes/qpainter/qpainterbackend.h" 0027 0028 #include <drm_fourcc.h> 0029 #include <fcntl.h> 0030 #include <sys/utsname.h> 0031 0032 using namespace KWin; 0033 0034 static std::unique_ptr<MockGpu> findPrimaryDevice(int crtcCount) 0035 { 0036 const int deviceCount = drmGetDevices2(0, nullptr, 0); 0037 if (deviceCount <= 0) { 0038 return nullptr; 0039 } 0040 0041 QList<drmDevice *> devices(deviceCount); 0042 if (drmGetDevices2(0, devices.data(), devices.size()) < 0) { 0043 return nullptr; 0044 } 0045 auto deviceCleanup = qScopeGuard([&devices]() { 0046 drmFreeDevices(devices.data(), devices.size()); 0047 }); 0048 0049 for (drmDevice *device : std::as_const(devices)) { 0050 if (device->available_nodes & (1 << DRM_NODE_PRIMARY)) { 0051 int fd = open(device->nodes[DRM_NODE_PRIMARY], O_RDWR | O_CLOEXEC); 0052 if (fd != -1) { 0053 return std::make_unique<MockGpu>(fd, device->nodes[DRM_NODE_PRIMARY], crtcCount); 0054 } 0055 } 0056 } 0057 0058 return nullptr; 0059 } 0060 0061 class DrmTest : public QObject 0062 { 0063 Q_OBJECT 0064 private Q_SLOTS: 0065 void testAmsDetection(); 0066 void testOutputDetection(); 0067 void testZeroModesHandling(); 0068 void testModeGeneration_data(); 0069 void testModeGeneration(); 0070 void testConnectorLifetime(); 0071 void testModeset_data(); 0072 void testModeset(); 0073 }; 0074 0075 static void verifyCleanup(MockGpu *mockGpu) 0076 { 0077 QVERIFY(mockGpu->drmConnectors.isEmpty()); 0078 QVERIFY(mockGpu->drmEncoders.isEmpty()); 0079 QVERIFY(mockGpu->drmCrtcs.isEmpty()); 0080 QVERIFY(mockGpu->drmPlanes.isEmpty()); 0081 QVERIFY(mockGpu->drmPlaneRes.isEmpty()); 0082 QVERIFY(mockGpu->fbs.isEmpty()); 0083 QVERIFY(mockGpu->drmProps.isEmpty()); 0084 QVERIFY(mockGpu->drmObjectProperties.isEmpty()); 0085 QVERIFY(mockGpu->drmPropertyBlobs.isEmpty()); 0086 } 0087 0088 void DrmTest::testAmsDetection() 0089 { 0090 const auto session = Session::create(Session::Type::Noop); 0091 const auto backend = std::make_unique<DrmBackend>(session.get()); 0092 0093 // gpu without planes should use legacy mode 0094 { 0095 const auto mockGpu = findPrimaryDevice(0); 0096 auto gpu = std::make_unique<DrmGpu>(backend.get(), mockGpu->devNode, mockGpu->fd, 0); 0097 QVERIFY(!gpu->atomicModeSetting()); 0098 } 0099 0100 // gpu with planes should use AMS 0101 { 0102 const auto mockGpu = findPrimaryDevice(0); 0103 mockGpu->planes << std::make_shared<MockPlane>(mockGpu.get(), PlaneType::Primary, 0); 0104 auto gpu = std::make_unique<DrmGpu>(backend.get(), mockGpu->devNode, mockGpu->fd, 0); 0105 gpu = std::make_unique<DrmGpu>(backend.get(), mockGpu->devNode, mockGpu->fd, 0); 0106 QVERIFY(gpu->atomicModeSetting()); 0107 } 0108 0109 // but not if the kernel doesn't allow it 0110 { 0111 const auto mockGpu = findPrimaryDevice(0); 0112 mockGpu->deviceCaps[MOCKDRM_DEVICE_CAP_ATOMIC] = 0; 0113 auto gpu = std::make_unique<DrmGpu>(backend.get(), mockGpu->devNode, mockGpu->fd, 0); 0114 QVERIFY(!gpu->atomicModeSetting()); 0115 gpu.reset(); 0116 verifyCleanup(mockGpu.get()); 0117 } 0118 } 0119 0120 void DrmTest::testOutputDetection() 0121 { 0122 const auto mockGpu = findPrimaryDevice(5); 0123 0124 const auto one = std::make_shared<MockConnector>(mockGpu.get()); 0125 const auto two = std::make_shared<MockConnector>(mockGpu.get()); 0126 const auto vr = std::make_shared<MockConnector>(mockGpu.get(), true); 0127 mockGpu->connectors.push_back(one); 0128 mockGpu->connectors.push_back(two); 0129 mockGpu->connectors.push_back(vr); 0130 0131 const auto session = Session::create(Session::Type::Noop); 0132 const auto backend = std::make_unique<DrmBackend>(session.get()); 0133 const auto renderBackend = backend->createQPainterBackend(); 0134 auto gpu = std::make_unique<DrmGpu>(backend.get(), mockGpu->devNode, mockGpu->fd, 0); 0135 QVERIFY(gpu->updateOutputs()); 0136 0137 // 3 outputs should be detected, one of them non-desktop 0138 const auto outputs = gpu->drmOutputs(); 0139 QCOMPARE(outputs.size(), 3); 0140 const auto vrOutput = std::find_if(outputs.begin(), outputs.end(), [](const auto &output) { 0141 return output->isNonDesktop(); 0142 }); 0143 QVERIFY(vrOutput != outputs.end()); 0144 QVERIFY(static_cast<DrmOutput *>(*vrOutput)->connector()->id() == vr->id); 0145 0146 // test hotunplugging 0147 mockGpu->connectors.removeOne(one); 0148 QVERIFY(gpu->updateOutputs()); 0149 QCOMPARE(gpu->drmOutputs().size(), 2); 0150 0151 // test hotplugging 0152 mockGpu->connectors.push_back(one); 0153 QVERIFY(gpu->updateOutputs()); 0154 QCOMPARE(gpu->drmOutputs().size(), 3); 0155 0156 // connector state changing to disconnected should count as a hotunplug 0157 one->connection = DRM_MODE_DISCONNECTED; 0158 QVERIFY(gpu->updateOutputs()); 0159 QCOMPARE(gpu->drmOutputs().size(), 2); 0160 0161 // don't crash if all connectors are disconnected 0162 two->connection = DRM_MODE_DISCONNECTED; 0163 vr->connection = DRM_MODE_DISCONNECTED; 0164 QVERIFY(gpu->updateOutputs()); 0165 QVERIFY(gpu->drmOutputs().empty()); 0166 0167 gpu.reset(); 0168 verifyCleanup(mockGpu.get()); 0169 } 0170 0171 void DrmTest::testZeroModesHandling() 0172 { 0173 const auto mockGpu = findPrimaryDevice(5); 0174 0175 const auto conn = std::make_shared<MockConnector>(mockGpu.get()); 0176 mockGpu->connectors.push_back(conn); 0177 0178 const auto session = Session::create(Session::Type::Noop); 0179 const auto backend = std::make_unique<DrmBackend>(session.get()); 0180 const auto renderBackend = backend->createQPainterBackend(); 0181 auto gpu = std::make_unique<DrmGpu>(backend.get(), mockGpu->devNode, mockGpu->fd, 0); 0182 0183 // connector with zero modes should be ignored 0184 conn->modes.clear(); 0185 QVERIFY(gpu->updateOutputs()); 0186 QVERIFY(gpu->drmOutputs().empty()); 0187 0188 // once it has modes, it should be detected 0189 conn->addMode(1920, 1080, 60); 0190 QVERIFY(gpu->updateOutputs()); 0191 QCOMPARE(gpu->drmOutputs().size(), 1); 0192 0193 // if an update says it has no modes anymore but it's still connected, ignore that 0194 conn->modes.clear(); 0195 QVERIFY(gpu->updateOutputs()); 0196 QCOMPARE(gpu->drmOutputs().size(), 1); 0197 QVERIFY(!gpu->drmOutputs().constFirst()->modes().empty()); 0198 0199 gpu.reset(); 0200 verifyCleanup(mockGpu.get()); 0201 } 0202 0203 void DrmTest::testModeGeneration_data() 0204 { 0205 QTest::addColumn<QSize>("nativeMode"); 0206 QTest::addColumn<QList<QSize>>("expectedModes"); 0207 0208 QTest::newRow("2160p") << QSize(3840, 2160) << QList<QSize>{ 0209 QSize(1600, 1200), 0210 QSize(1280, 1024), 0211 QSize(1024, 768), 0212 QSize(2560, 1600), 0213 QSize(1920, 1200), 0214 QSize(1280, 800), 0215 QSize(3840, 2160), 0216 QSize(3200, 1800), 0217 QSize(2880, 1620), 0218 QSize(2560, 1440), 0219 QSize(1920, 1080), 0220 QSize(1600, 900), 0221 QSize(1368, 768), 0222 QSize(1280, 720), 0223 }; 0224 QTest::newRow("1440p") << QSize(2560, 1440) << QList<QSize>{ 0225 QSize(1600, 1200), 0226 QSize(1280, 1024), 0227 QSize(1024, 768), 0228 QSize(1920, 1200), 0229 QSize(1280, 800), 0230 QSize(2560, 1440), 0231 QSize(1920, 1080), 0232 QSize(1600, 900), 0233 QSize(1368, 768), 0234 QSize(1280, 720), 0235 }; 0236 QTest::newRow("1080p") << QSize(1920, 1080) << QList<QSize>{ 0237 QSize(1280, 1024), 0238 QSize(1024, 768), 0239 QSize(1280, 800), 0240 QSize(1920, 1080), 0241 QSize(1600, 900), 0242 QSize(1368, 768), 0243 QSize(1280, 720), 0244 }; 0245 0246 QTest::newRow("2160p 21:9") << QSize(5120, 2160) << QList<QSize>{ 0247 QSize(5120, 2160), 0248 QSize(1600, 1200), 0249 QSize(1280, 1024), 0250 QSize(1024, 768), 0251 QSize(2560, 1600), 0252 QSize(1920, 1200), 0253 QSize(1280, 800), 0254 QSize(3840, 2160), 0255 QSize(3200, 1800), 0256 QSize(2880, 1620), 0257 QSize(2560, 1440), 0258 QSize(1920, 1080), 0259 QSize(1600, 900), 0260 QSize(1368, 768), 0261 QSize(1280, 720), 0262 }; 0263 QTest::newRow("1440p 21:9") << QSize(3440, 1440) << QList<QSize>{ 0264 QSize(3440, 1440), 0265 QSize(1600, 1200), 0266 QSize(1280, 1024), 0267 QSize(1024, 768), 0268 QSize(1920, 1200), 0269 QSize(1280, 800), 0270 QSize(2560, 1440), 0271 QSize(1920, 1080), 0272 QSize(1600, 900), 0273 QSize(1368, 768), 0274 QSize(1280, 720), 0275 }; 0276 QTest::newRow("1080p 21:9") << QSize(2560, 1080) << QList<QSize>{ 0277 QSize(2560, 1080), 0278 QSize(1280, 1024), 0279 QSize(1024, 768), 0280 QSize(1280, 800), 0281 QSize(1920, 1080), 0282 QSize(1600, 900), 0283 QSize(1368, 768), 0284 QSize(1280, 720), 0285 }; 0286 } 0287 0288 void DrmTest::testModeGeneration() 0289 { 0290 const auto mockGpu = findPrimaryDevice(5); 0291 0292 const auto conn = std::make_shared<MockConnector>(mockGpu.get()); 0293 mockGpu->connectors.push_back(conn); 0294 0295 const auto session = Session::create(Session::Type::Noop); 0296 const auto backend = std::make_unique<DrmBackend>(session.get()); 0297 const auto renderBackend = backend->createQPainterBackend(); 0298 auto gpu = std::make_unique<DrmGpu>(backend.get(), mockGpu->devNode, mockGpu->fd, 0); 0299 0300 QFETCH(QSize, nativeMode); 0301 QFETCH(QList<QSize>, expectedModes); 0302 0303 conn->modes.clear(); 0304 conn->addMode(nativeMode.width(), nativeMode.height(), 60); 0305 QVERIFY(gpu->updateOutputs()); 0306 QCOMPARE(gpu->drmOutputs().size(), 1); 0307 // no mode generation without the scaling property 0308 QCOMPARE(gpu->drmOutputs().front()->modes().size(), 1); 0309 0310 mockGpu->connectors.removeAll(conn); 0311 QVERIFY(gpu->updateOutputs()); 0312 0313 conn->props.emplace_back(conn.get(), QStringLiteral("scaling mode"), 0, DRM_MODE_PROP_ENUM, QList<QByteArray>{"None", "Full", "Center", "Full aspect"}); 0314 mockGpu->connectors.push_back(conn); 0315 QVERIFY(gpu->updateOutputs()); 0316 0317 DrmOutput *const output = gpu->drmOutputs().front(); 0318 QCOMPARE(output->modes().size(), expectedModes.size()); 0319 for (const auto &mode : output->modes()) { 0320 QVERIFY(expectedModes.contains(mode->size())); 0321 QVERIFY(mode->size().width() <= nativeMode.width()); 0322 QVERIFY(mode->size().height() <= nativeMode.height()); 0323 QVERIFY(mode->refreshRate() <= 60000); 0324 } 0325 0326 gpu.reset(); 0327 verifyCleanup(mockGpu.get()); 0328 } 0329 0330 void DrmTest::testConnectorLifetime() 0331 { 0332 // don't crash if output lifetime is extended beyond the connector 0333 const auto mockGpu = findPrimaryDevice(5); 0334 0335 const auto conn = std::make_shared<MockConnector>(mockGpu.get()); 0336 mockGpu->connectors.push_back(conn); 0337 0338 const auto session = Session::create(Session::Type::Noop); 0339 const auto backend = std::make_unique<DrmBackend>(session.get()); 0340 const auto renderBackend = backend->createQPainterBackend(); 0341 auto gpu = std::make_unique<DrmGpu>(backend.get(), mockGpu->devNode, mockGpu->fd, 0); 0342 0343 QVERIFY(gpu->updateOutputs()); 0344 QCOMPARE(gpu->drmOutputs().size(), 1); 0345 0346 DrmOutput *const output = gpu->drmOutputs().front(); 0347 0348 output->ref(); 0349 mockGpu->connectors.clear(); 0350 QVERIFY(gpu->updateOutputs()); 0351 output->unref(); 0352 0353 gpu.reset(); 0354 verifyCleanup(mockGpu.get()); 0355 } 0356 0357 void DrmTest::testModeset_data() 0358 { 0359 QTest::addColumn<int>("AMS"); 0360 // TODO to uncomment this, implement page flip callbacks 0361 // QTest::newRow("disabled") << 0; 0362 QTest::newRow("enabled") << 1; 0363 } 0364 0365 void DrmTest::testModeset() 0366 { 0367 // test if doing a modeset would succeed 0368 QFETCH(int, AMS); 0369 const auto mockGpu = findPrimaryDevice(5); 0370 mockGpu->deviceCaps[MOCKDRM_DEVICE_CAP_ATOMIC] = AMS; 0371 0372 const auto conn = std::make_shared<MockConnector>(mockGpu.get()); 0373 mockGpu->connectors.push_back(conn); 0374 0375 const auto session = Session::create(Session::Type::Noop); 0376 const auto backend = std::make_unique<DrmBackend>(session.get()); 0377 const auto renderBackend = backend->createQPainterBackend(); 0378 auto gpu = std::make_unique<DrmGpu>(backend.get(), mockGpu->devNode, mockGpu->fd, 0); 0379 0380 QVERIFY(gpu->updateOutputs()); 0381 QCOMPARE(gpu->drmOutputs().size(), 1); 0382 const auto output = gpu->drmOutputs().front(); 0383 const auto layer = renderBackend->primaryLayer(output); 0384 layer->beginFrame(); 0385 output->renderLoop()->prepareNewFrame(); 0386 output->renderLoop()->beginPaint(); 0387 layer->endFrame(infiniteRegion(), infiniteRegion()); 0388 QVERIFY(output->present(std::make_shared<OutputFrame>(output->renderLoop()))); 0389 0390 gpu.reset(); 0391 verifyCleanup(mockGpu.get()); 0392 } 0393 0394 QTEST_GUILESS_MAIN(DrmTest) 0395 #include "drmTest.moc"