add autotest for the drm platform
This commit is contained in:
parent
85d46016a2
commit
a587e426f8
7 changed files with 1773 additions and 0 deletions
|
@ -4,6 +4,7 @@ add_subdirectory(libkwineffects)
|
|||
add_subdirectory(integration)
|
||||
add_subdirectory(libinput)
|
||||
add_subdirectory(tabbox)
|
||||
add_subdirectory(drm)
|
||||
|
||||
########################################################
|
||||
# Test WindowPaintData
|
||||
|
|
62
autotests/drm/CMakeLists.txt
Normal file
62
autotests/drm/CMakeLists.txt
Normal 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
162
autotests/drm/drmTest.cpp
Normal 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
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
205
autotests/drm/mock_drm.h
Normal 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;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -33,6 +33,7 @@
|
|||
// Qt
|
||||
#include <QCoreApplication>
|
||||
#include <QSocketNotifier>
|
||||
#include <QStringBuilder>
|
||||
// system
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue