kwin/src/plugins/platforms/drm/drm_gpu.cpp

360 lines
11 KiB
C++
Raw Normal View History

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_gpu.h"
#include "drm_backend.h"
#include "drm_output.h"
#include "drm_object_connector.h"
#include "drm_object_crtc.h"
#include "abstract_egl_backend.h"
#include "logging.h"
2021-02-19 11:59:43 +00:00
#include "logind.h"
#if HAVE_GBM
#include "egl_gbm_backend.h"
#include <gbm.h>
#include "gbm_dmabuf.h"
#endif
// system
#include <algorithm>
#include <unistd.h>
// drm
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <libdrm/drm_mode.h>
namespace KWin
{
DrmGpu::DrmGpu(DrmBackend *backend, QByteArray devNode, int fd, int drmId) : m_backend(backend), m_devNode(devNode), m_fd(fd), m_drmId(drmId), m_atomicModeSetting(false), m_gbmDevice(nullptr)
{
uint64_t capability = 0;
if (drmGetCap(fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) {
m_cursorSize.setWidth(capability);
} else {
m_cursorSize.setWidth(64);
}
if (drmGetCap(fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) {
m_cursorSize.setHeight(capability);
} else {
m_cursorSize.setHeight(64);
}
2020-11-27 19:57:24 +00:00
2020-11-19 08:52:29 +00:00
int ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &capability);
if (ret == 0 && capability == 1) {
m_presentationClock = CLOCK_MONOTONIC;
} else {
m_presentationClock = CLOCK_REALTIME;
}
// find out if this GPU is using the NVidia proprietary driver
DrmScopedPointer<drmVersion> version(drmGetVersion(fd));
m_useEglStreams = strstr(version->name, "nvidia-drm");
m_deleteBufferAfterPageFlip = !m_useEglStreams;
2021-02-19 11:59:43 +00:00
m_socketNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
connect(m_socketNotifier, &QSocketNotifier::activated, this, &DrmGpu::dispatchEvents);
}
DrmGpu::~DrmGpu()
{
2021-02-22 18:17:23 +00:00
waitIdle();
if (m_eglDisplay != EGL_NO_DISPLAY) {
eglTerminate(m_eglDisplay);
}
#if HAVE_GBM
gbm_device_destroy(m_gbmDevice);
#endif
qDeleteAll(m_crtcs);
qDeleteAll(m_connectors);
qDeleteAll(m_planes);
2021-02-19 11:59:43 +00:00
delete m_socketNotifier;
LogindIntegration::self()->releaseDevice(m_fd);
}
2020-11-19 08:52:29 +00:00
clockid_t DrmGpu::presentationClock() const
{
return m_presentationClock;
}
void DrmGpu::tryAMS()
{
m_atomicModeSetting = false;
if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) {
2021-02-18 09:25:38 +00:00
m_atomicModeSetting = true;
DrmScopedPointer<drmModePlaneRes> planeResources(drmModeGetPlaneResources(m_fd));
if (!planeResources) {
qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode;
2021-02-15 17:28:58 +00:00
m_atomicModeSetting = false;
return;
}
2021-02-15 17:28:58 +00:00
qCDebug(KWIN_DRM) << "Using Atomic Mode Setting on gpu" << m_devNode;
qCDebug(KWIN_DRM) << "Number of planes on GPU" << m_devNode << ":" << planeResources->count_planes;
// create the plane objects
for (unsigned int i = 0; i < planeResources->count_planes; ++i) {
DrmScopedPointer<drmModePlane> kplane(drmModeGetPlane(m_fd, planeResources->planes[i]));
DrmPlane *p = new DrmPlane(kplane->plane_id, m_fd);
if (p->init()) {
m_planes << p;
} else {
delete p;
}
}
2021-02-15 17:28:58 +00:00
if (m_planes.isEmpty()) {
qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << m_devNode;
m_atomicModeSetting = false;
}
m_unusedPlanes = m_planes;
} else {
qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << m_devNode;
}
}
bool DrmGpu::updateOutputs()
{
auto oldConnectors = m_connectors;
auto oldCrtcs = m_crtcs;
DrmScopedPointer<drmModeRes> resources(drmModeGetResources(m_fd));
if (!resources) {
qCWarning(KWIN_DRM) << "drmModeGetResources failed";
return false;
}
for (int i = 0; i < resources->count_connectors; ++i) {
const uint32_t currentConnector = resources->connectors[i];
auto it = std::find_if(m_connectors.constBegin(), m_connectors.constEnd(), [currentConnector] (DrmConnector *c) { return c->id() == currentConnector; });
if (it == m_connectors.constEnd()) {
auto c = new DrmConnector(currentConnector, m_fd);
if (!c->init()) {
delete c;
continue;
}
if (c->isNonDesktop()) {
delete c;
continue;
}
2021-02-05 18:55:07 +00:00
if (!c->isConnected()) {
delete c;
continue;
}
m_connectors << c;
} else {
oldConnectors.removeOne(*it);
}
}
for (int i = 0; i < resources->count_crtcs; ++i) {
const uint32_t currentCrtc = resources->crtcs[i];
auto it = std::find_if(m_crtcs.constBegin(), m_crtcs.constEnd(), [currentCrtc] (DrmCrtc *c) { return c->id() == currentCrtc; });
if (it == m_crtcs.constEnd()) {
auto c = new DrmCrtc(currentCrtc, m_backend, this, i);
if (!c->init()) {
delete c;
continue;
}
m_crtcs << c;
} else {
oldCrtcs.removeOne(*it);
}
}
for (auto c : qAsConst(oldConnectors)) {
m_connectors.removeOne(c);
}
for (auto c : qAsConst(oldCrtcs)) {
m_crtcs.removeOne(c);
}
2020-11-27 19:57:24 +00:00
QVector<DrmOutput*> connectedOutputs;
QVector<DrmConnector*> pendingConnectors;
// split up connected connectors in already or not yet assigned ones
for (DrmConnector *con : qAsConst(m_connectors)) {
if (!con->isConnected()) {
continue;
}
if (DrmOutput *o = findOutput(con->id())) {
connectedOutputs << o;
} else {
pendingConnectors << con;
}
}
// check for outputs which got removed
QVector<DrmOutput*> removedOutputs;
auto it = m_outputs.begin();
while (it != m_outputs.end()) {
if (connectedOutputs.contains(*it)) {
it++;
continue;
}
DrmOutput *removed = *it;
it = m_outputs.erase(it);
removedOutputs.append(removed);
}
2020-11-27 19:57:24 +00:00
for (DrmConnector *con : qAsConst(pendingConnectors)) {
DrmScopedPointer<drmModeConnector> connector(drmModeGetConnector(m_fd, con->id()));
if (!connector) {
continue;
}
if (connector->count_modes == 0) {
continue;
}
bool outputDone = false;
QVector<uint32_t> encoders = con->encoders();
for (auto encId : qAsConst(encoders)) {
DrmScopedPointer<drmModeEncoder> encoder(drmModeGetEncoder(m_fd, encId));
if (!encoder) {
continue;
}
for (DrmCrtc *crtc : qAsConst(m_crtcs)) {
if (!(encoder->possible_crtcs & (1 << crtc->resIndex()))) {
continue;
}
2020-11-27 19:57:24 +00:00
// check if crtc isn't used yet -- currently we don't allow multiple outputs on one crtc (cloned mode)
auto it = std::find_if(connectedOutputs.constBegin(), connectedOutputs.constEnd(),
[crtc] (DrmOutput *o) {
return o->m_crtc == crtc;
}
);
if (it != connectedOutputs.constEnd()) {
continue;
}
// we found a suitable encoder+crtc
// TODO: we could avoid these lib drm calls if we store all struct data in DrmCrtc and DrmConnector in the beginning
DrmScopedPointer<drmModeCrtc> modeCrtc(drmModeGetCrtc(m_fd, crtc->id()));
if (!modeCrtc) {
continue;
}
DrmOutput *output = new DrmOutput(this->m_backend, this);
output->m_conn = con;
output->m_crtc = crtc;
2020-12-15 20:20:10 +00:00
output->m_mode = connector->modes[0];
2021-02-15 17:28:58 +00:00
output->m_primaryPlane = getCompatiblePlane(DrmPlane::TypeIndex::Primary, crtc);
output->m_cursorPlane = getCompatiblePlane(DrmPlane::TypeIndex::Cursor, crtc);
2020-12-15 20:20:10 +00:00
qCDebug(KWIN_DRM) << "For new output use mode " << output->m_mode.name << output->m_mode.hdisplay << output->m_mode.vdisplay;
if (!output->init(connector.data())) {
qCWarning(KWIN_DRM) << "Failed to create output for connector " << con->id();
delete output;
continue;
}
if (!output->initCursor(m_cursorSize)) {
m_backend->setSoftwareCursorForced(true);
}
qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid() << "on gpu" << m_devNode;
connectedOutputs << output;
emit outputAdded(output);
outputDone = true;
break;
}
if (outputDone) {
break;
}
}
}
std::sort(connectedOutputs.begin(), connectedOutputs.end(), [] (DrmOutput *a, DrmOutput *b) { return a->m_conn->id() < b->m_conn->id(); });
m_outputs = connectedOutputs;
2020-11-27 19:57:24 +00:00
for(DrmOutput *removedOutput : removedOutputs) {
emit outputRemoved(removedOutput);
removedOutput->teardown();
removedOutput->m_crtc = nullptr;
2021-02-05 18:55:07 +00:00
m_connectors.removeOne(removedOutput->m_conn);
delete removedOutput->m_conn;
removedOutput->m_conn = nullptr;
2021-02-15 17:28:58 +00:00
if (removedOutput->m_primaryPlane) {
m_unusedPlanes << removedOutput->m_primaryPlane;
}
}
2020-11-27 19:57:24 +00:00
qDeleteAll(oldConnectors);
qDeleteAll(oldCrtcs);
return true;
}
DrmOutput *DrmGpu::findOutput(quint32 connector)
{
auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [connector] (DrmOutput *o) {
return o->m_conn->id() == connector;
});
if (it != m_outputs.constEnd()) {
return *it;
}
return nullptr;
}
2021-02-15 17:28:58 +00:00
DrmPlane *DrmGpu::getCompatiblePlane(DrmPlane::TypeIndex typeIndex, DrmCrtc *crtc)
{
for (auto plane : m_unusedPlanes) {
if (plane->type() != typeIndex) {
continue;
}
if (plane->isCrtcSupported(crtc->resIndex())) {
m_unusedPlanes.removeOne(plane);
return plane;
}
}
return nullptr;
}
void DrmGpu::dispatchEvents()
{
if (!LogindIntegration::self()->isActiveSession()) {
return;
}
drmEventContext context = {};
context.version = 2;
context.page_flip_handler = DrmBackend::pageFlipHandler;
drmHandleEvent(m_fd, &context);
}
2021-02-22 18:17:23 +00:00
void DrmGpu::waitIdle()
{
m_socketNotifier->setEnabled(false);
while (true) {
const bool idle = std::all_of(m_outputs.constBegin(), m_outputs.constEnd(), [](DrmOutput *output){
return !output->m_pageFlipPending;
});
if (idle) {
break;
}
fd_set set;
FD_ZERO(&set);
FD_SET(m_fd, &set);
timeval timeout;
timeout.tv_sec = 30;
timeout.tv_usec = 0;
const int descriptorCount = select(m_fd + 1, &set, nullptr, nullptr, &timeout);
if (descriptorCount < 0) {
qCWarning(KWIN_DRM) << "select() in DrmGpu::waitIdle failed:" << strerror(errno);
break;
} else if (FD_ISSET(m_fd, &set)) {
dispatchEvents();
} else {
qCWarning(KWIN_DRM) << "No drm events for gpu" << m_devNode << "within last 30 seconds";
break;
}
};
m_socketNotifier->setEnabled(true);
}
}