add autotest for the drm platform

This commit is contained in:
Xaver Hugl 2021-07-18 22:21:59 +02:00
parent 85d46016a2
commit a587e426f8
7 changed files with 1773 additions and 0 deletions

View file

@ -4,6 +4,7 @@ add_subdirectory(libkwineffects)
add_subdirectory(integration)
add_subdirectory(libinput)
add_subdirectory(tabbox)
add_subdirectory(drm)
########################################################
# Test WindowPaintData

View file

@ -0,0 +1,62 @@
set(mockDRM_SRCS
mock_drm.cpp
../../src/backends/drm/drm_abstract_output.cpp
../../src/backends/drm/drm_backend.cpp
../../src/backends/drm/drm_buffer.cpp
../../src/backends/drm/drm_buffer_gbm.cpp
../../src/backends/drm/drm_dmabuf_feedback.cpp
../../src/backends/drm/drm_dumb_buffer.cpp
../../src/backends/drm/drm_dumb_swapchain.cpp
../../src/backends/drm/drm_egl_backend.cpp
../../src/backends/drm/drm_egl_cursor_layer.cpp
../../src/backends/drm/drm_egl_layer.cpp
../../src/backends/drm/drm_egl_layer_surface.cpp
../../src/backends/drm/drm_gbm_surface.cpp
../../src/backends/drm/drm_gpu.cpp
../../src/backends/drm/drm_layer.cpp
../../src/backends/drm/drm_logging.cpp
../../src/backends/drm/drm_connector.cpp
../../src/backends/drm/drm_object.cpp
../../src/backends/drm/drm_crtc.cpp
../../src/backends/drm/drm_plane.cpp
../../src/backends/drm/drm_output.cpp
../../src/backends/drm/drm_pipeline.cpp
../../src/backends/drm/drm_pipeline_legacy.cpp
../../src/backends/drm/drm_property.cpp
../../src/backends/drm/drm_qpainter_backend.cpp
../../src/backends/drm/drm_qpainter_layer.cpp
../../src/backends/drm/drm_shadow_buffer.cpp
../../src/backends/drm/drm_virtual_egl_layer.cpp
../../src/backends/drm/drm_virtual_output.cpp
)
include_directories(${Libdrm_INCLUDE_DIRS})
add_library(LibDrmTest STATIC ${mockDRM_SRCS})
target_link_libraries(LibDrmTest
Qt::Gui
Qt::Widgets
KF5::ConfigCore
KF5::WindowSystem
KF5::CoreAddons
KF5::I18n
XCB::XCB
PkgConfig::Libxcvt
gbm::gbm
kwin
)
target_include_directories(LibDrmTest
PUBLIC
../../src
../../src/platformsupport/scenes/opengl
../../src/platformsupport/scenes/qpainter
../../src/backends/drm/
)
########################################################
# Tests
########################################################
add_executable(testDrm drmTest.cpp)
target_link_libraries(testDrm LibDrmTest Qt::Test)
add_test(NAME kwin-testDrm COMMAND testDrm)
ecm_mark_as_test(testDrm)

162
autotests/drm/drmTest.cpp Normal file
View file

@ -0,0 +1,162 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <QtTest>
#include "mock_drm.h"
#include "drm_backend.h"
#include "drm_dumb_buffer.h"
#include "drm_egl_backend.h"
#include "drm_gpu.h"
#include "drm_connector.h"
#include "drm_crtc.h"
#include "drm_plane.h"
#include "drm_output.h"
#include "drm_pipeline.h"
#include "drm_pointer.h"
#include "qpainterbackend.h"
#include "core/session.h"
#include <drm_fourcc.h>
using namespace KWin;
class DrmTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testAmsDetection();
void testOutputDetection();
void testZeroModesHandling();
};
static void verifyCleanup(MockGpu *mockGpu)
{
QVERIFY(mockGpu->drmConnectors.isEmpty());
QVERIFY(mockGpu->drmEncoders.isEmpty());
QVERIFY(mockGpu->drmCrtcs.isEmpty());
QVERIFY(mockGpu->drmPlanes.isEmpty());
QVERIFY(mockGpu->drmPlaneRes.isEmpty());
QVERIFY(mockGpu->fbs.isEmpty());
QVERIFY(mockGpu->drmProps.isEmpty());
QVERIFY(mockGpu->drmObjectProperties.isEmpty());
QVERIFY(mockGpu->drmPropertyBlobs.isEmpty());
}
void DrmTest::testAmsDetection()
{
const auto mockGpu = std::make_unique<MockGpu>(1, 0);
const auto session = Session::create(Session::Type::Noop);
const auto backend = std::make_unique<DrmBackend>(session.get());
// gpu without planes should use legacy mode
auto gpu = std::make_unique<DrmGpu>(backend.get(), "legacy", 1, 0);
QVERIFY(!gpu->atomicModeSetting());
// gpu with planes should use AMS
mockGpu->planes << std::make_shared<MockPlane>(mockGpu.get(), PlaneType::Primary, 0);
gpu = std::make_unique<DrmGpu>(backend.get(), "AMS", 1, 0);
QVERIFY(gpu->atomicModeSetting());
// but not if the kernel doesn't allow it
mockGpu->deviceCaps[MOCKDRM_DEVICE_CAP_ATOMIC] = 0;
gpu = std::make_unique<DrmGpu>(backend.get(), "legacy 2", 1, 0);
QVERIFY(!gpu->atomicModeSetting());
gpu.reset();
verifyCleanup(mockGpu.get());
}
void DrmTest::testOutputDetection()
{
const auto mockGpu = std::make_unique<MockGpu>(1, 5);
const auto one = std::make_shared<MockConnector>(mockGpu.get());
const auto two = std::make_shared<MockConnector>(mockGpu.get());
const auto vr = std::make_shared<MockConnector>(mockGpu.get(), true);
mockGpu->connectors.push_back(one);
mockGpu->connectors.push_back(two);
mockGpu->connectors.push_back(vr);
const auto session = Session::create(Session::Type::Noop);
const auto backend = std::make_unique<DrmBackend>(session.get());
const auto renderBackend = backend->createQPainterBackend();
auto gpu = std::make_unique<DrmGpu>(backend.get(), "test", 1, 0);
QVERIFY(gpu->updateOutputs());
// 3 outputs should be detected, one of them non-desktop
const auto outputs = gpu->drmOutputs();
QCOMPARE(outputs.size(), 3);
const auto vrOutput = std::find_if(outputs.begin(), outputs.end(), [](const auto &output) {
return output->isNonDesktop();
});
QVERIFY(vrOutput != outputs.end());
QVERIFY(static_cast<DrmOutput *>(*vrOutput)->connector()->id() == vr->id);
// test hotunplugging
mockGpu->connectors.removeOne(one);
QVERIFY(gpu->updateOutputs());
QCOMPARE(gpu->drmOutputs().size(), 2);
// test hotplugging
mockGpu->connectors.push_back(one);
QVERIFY(gpu->updateOutputs());
QCOMPARE(gpu->drmOutputs().size(), 3);
// connector state changing to disconnected should count as a hotunplug
one->connection = DRM_MODE_DISCONNECTED;
QVERIFY(gpu->updateOutputs());
QCOMPARE(gpu->drmOutputs().size(), 2);
// don't crash if all connectors are disconnected
two->connection = DRM_MODE_DISCONNECTED;
vr->connection = DRM_MODE_DISCONNECTED;
QVERIFY(gpu->updateOutputs());
QVERIFY(gpu->drmOutputs().empty());
gpu.reset();
verifyCleanup(mockGpu.get());
}
void DrmTest::testZeroModesHandling()
{
const auto mockGpu = std::make_unique<MockGpu>(1, 5);
const auto conn = std::make_shared<MockConnector>(mockGpu.get());
mockGpu->connectors.push_back(conn);
const auto session = Session::create(Session::Type::Noop);
const auto backend = std::make_unique<DrmBackend>(session.get());
const auto renderBackend = backend->createQPainterBackend();
auto gpu = std::make_unique<DrmGpu>(backend.get(), "test", 1, 0);
// connector with zero modes should be ignored
conn->modes.clear();
QVERIFY(gpu->updateOutputs());
QVERIFY(gpu->drmOutputs().empty());
// once it has modes, it should be detected
conn->addMode(1920, 1080, 60);
QVERIFY(gpu->updateOutputs());
QCOMPARE(gpu->drmOutputs().size(), 1);
// if an update says it has no modes anymore but it's still connected, ignore that
conn->modes.clear();
QVERIFY(gpu->updateOutputs());
QCOMPARE(gpu->drmOutputs().size(), 1);
QVERIFY(!gpu->drmOutputs().constFirst()->modes().empty());
gpu.reset();
verifyCleanup(mockGpu.get());
}
QTEST_GUILESS_MAIN(DrmTest)
#include "drmTest.moc"

1338
autotests/drm/mock_drm.cpp Normal file

File diff suppressed because it is too large Load diff

205
autotests/drm/mock_drm.h Normal file
View file

@ -0,0 +1,205 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <QMap>
#include <QRect>
#include <QVector>
#include <memory>
class MockGpu;
class MockFb;
class MockCrtc;
class MockEncoder;
class MockObject;
class MockDumbBuffer;
class MockPlane;
class MockProperty {
public:
MockProperty(MockObject *obj, QString name, uint64_t initialValue, uint32_t flags, QVector<QByteArray> enums = {});
~MockProperty() = default;
MockObject *obj;
uint32_t id;
uint32_t flags;
QString name;
uint64_t value;
QVector<QByteArray> enums;
};
class MockPropertyBlob {
public:
MockPropertyBlob(MockGpu *gpu, const void *data, size_t size);
~MockPropertyBlob();
MockGpu *gpu;
uint32_t id;
void *data;
size_t size;
};
class MockObject {
public:
MockObject(MockGpu *gpu);
virtual ~MockObject();
uint64_t getProp(const QString &propName) const;
void setProp(const QString &propName, uint64_t value);
uint32_t getPropId(const QString &propName) const;
uint32_t id;
QVector<MockProperty> props;
MockGpu *gpu;
};
class MockConnector : public MockObject {
public:
MockConnector(MockGpu *gpu, bool nonDesktop = false);
MockConnector(const MockConnector &obj) = default;
~MockConnector() = default;
void addMode(uint32_t width, uint32_t height, float refreshRate, bool preferred = false);
drmModeConnection connection;
uint32_t type;
std::shared_ptr<MockEncoder> encoder;
QVector<drmModeModeInfo> modes;
};
class MockEncoder : public MockObject {
public:
MockEncoder(MockGpu *gpu, uint32_t possible_crtcs);
MockEncoder(const MockEncoder &obj) = default;
~MockEncoder() = default;
MockCrtc *crtc = nullptr;
uint32_t possible_crtcs;
uint32_t possible_clones = 0;
};
class MockCrtc : public MockObject {
public:
MockCrtc(MockGpu *gpu, const std::shared_ptr<MockPlane> &legacyPlane, int pipeIndex, int gamma_size = 255);
MockCrtc(const MockCrtc &obj) = default;
~MockCrtc() = default;
int pipeIndex;
int gamma_size;
drmModeModeInfo mode;
bool modeValid = true;
MockFb *currentFb = nullptr;
MockFb *nextFb = nullptr;
QRect cursorRect;
MockDumbBuffer *cursorBo = nullptr;
std::shared_ptr<MockPlane> legacyPlane;
};
enum class PlaneType {
Primary,
Overlay,
Cursor
};
class MockPlane : public MockObject {
public:
MockPlane(MockGpu *gpu, PlaneType type, int crtcIndex);
MockPlane(const MockPlane &obj) = default;
~MockPlane() = default;
MockFb *currentFb = nullptr;
MockFb *nextFb = nullptr;
int possibleCrtcs;
PlaneType type;
};
class MockFb {
public:
MockFb(MockGpu *gpu, uint32_t width, uint32_t height);
~MockFb();
uint32_t id;
uint32_t width, height;
MockGpu *gpu;
};
class MockDumbBuffer {
public:
MockDumbBuffer(MockGpu *gpu, uint32_t width, uint32_t height, uint32_t bpp);
~MockDumbBuffer();
uint32_t handle;
uint32_t pitch;
uint64_t size;
void *data;
MockGpu *gpu;
};
struct Prop {
uint32_t obj;
uint32_t prop;
uint64_t value;
};
struct _drmModeAtomicReq {
bool legacyEmulation = false;
QVector<Prop> props;
};
#define MOCKDRM_DEVICE_CAP_ATOMIC 0xFF
class MockGpu {
public:
MockGpu(int fd, int numCrtcs, int gammaSize = 255);
~MockGpu();
MockConnector *findConnector(uint32_t id) const;
MockCrtc *findCrtc(uint32_t id) const;
MockPlane *findPlane(uint32_t id) const;
MockPropertyBlob *getBlob(uint32_t id) const;
void flipPage(uint32_t crtcId);
int fd;
QByteArray name = QByteArrayLiteral("mock");
QMap<uint32_t, uint64_t> clientCaps;
QMap<uint32_t, uint64_t> deviceCaps;
uint32_t idCounter = 1;
QVector<MockObject*> objects;
QVector<std::shared_ptr<MockConnector>> connectors;
QVector<drmModeConnectorPtr> drmConnectors;
QVector<std::shared_ptr<MockEncoder>> encoders;
QVector<drmModeEncoderPtr> drmEncoders;
QVector<std::shared_ptr<MockCrtc>> crtcs;
QVector<drmModeCrtcPtr> drmCrtcs;
QVector<std::shared_ptr<MockPlane>> planes;
QVector<drmModePlanePtr> drmPlanes;
QVector<MockFb*> fbs;
QVector<std::shared_ptr<MockDumbBuffer>> dumbBuffers;
std::vector<std::unique_ptr<MockPropertyBlob>> propertyBlobs;
QVector<drmModeResPtr> resPtrs;
QVector<drmModePropertyPtr> drmProps;
QVector<drmModePropertyBlobPtr> drmPropertyBlobs;
QVector<drmModeObjectPropertiesPtr> drmObjectProperties;
QVector<drmModePlaneResPtr> drmPlaneRes;
};

View file

@ -33,6 +33,7 @@
// Qt
#include <QCoreApplication>
#include <QSocketNotifier>
#include <QStringBuilder>
// system
#include <algorithm>
#include <cerrno>

View file

@ -44,11 +44,15 @@ bool DrmDumbBuffer::map(QImage::Format format)
if (drmIoctl(m_gpu->fd(), DRM_IOCTL_MODE_MAP_DUMB, &mapArgs) != 0) {
return false;
}
#ifdef KWIN_UNIT_TEST
m_memory = reinterpret_cast<void *>(mapArgs.offset);
#else
void *address = mmap(nullptr, m_bufferSize, PROT_WRITE, MAP_SHARED, m_gpu->fd(), mapArgs.offset);
if (address == MAP_FAILED) {
return false;
}
m_memory = address;
#endif
m_image = std::make_unique<QImage>((uchar *)m_memory, m_size.width(), m_size.height(), m_strides[0], format);
return !m_image->isNull();
}