You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
951 lines
32 KiB
951 lines
32 KiB
/* |
|
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 <config-kwin.h> |
|
|
|
#include "core/gbmgraphicsbufferallocator.h" |
|
#include "core/session.h" |
|
#include "drm_backend.h" |
|
#include "drm_buffer.h" |
|
#include "drm_commit.h" |
|
#include "drm_connector.h" |
|
#include "drm_crtc.h" |
|
#include "drm_egl_backend.h" |
|
#include "drm_layer.h" |
|
#include "drm_logging.h" |
|
#include "drm_output.h" |
|
#include "drm_pipeline.h" |
|
#include "drm_plane.h" |
|
#include "drm_virtual_output.h" |
|
// system |
|
#include <algorithm> |
|
#include <errno.h> |
|
#include <fcntl.h> |
|
#include <poll.h> |
|
#include <unistd.h> |
|
// drm |
|
#include <drm_fourcc.h> |
|
#include <gbm.h> |
|
#include <libdrm/drm_mode.h> |
|
#include <xf86drm.h> |
|
#include <xf86drmMode.h> |
|
|
|
#ifndef DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT |
|
#define DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT 6 |
|
#endif |
|
|
|
namespace KWin |
|
{ |
|
|
|
DrmGpu::DrmGpu(DrmBackend *backend, const QString &devNode, int fd, dev_t deviceId) |
|
: m_fd(fd) |
|
, m_deviceId(deviceId) |
|
, m_devNode(devNode) |
|
, m_atomicModeSetting(false) |
|
, m_gbmDevice(nullptr) |
|
, m_platform(backend) |
|
{ |
|
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); |
|
} |
|
|
|
int ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &capability); |
|
if (ret == 0 && capability == 1) { |
|
m_presentationClock = CLOCK_MONOTONIC; |
|
} else { |
|
m_presentationClock = CLOCK_REALTIME; |
|
} |
|
|
|
m_addFB2ModifiersSupported = drmGetCap(fd, DRM_CAP_ADDFB2_MODIFIERS, &capability) == 0 && capability == 1; |
|
qCDebug(KWIN_DRM) << "drmModeAddFB2WithModifiers is" << (m_addFB2ModifiersSupported ? "supported" : "not supported") << "on GPU" << m_devNode; |
|
|
|
// find out what driver this kms device is using |
|
DrmUniquePtr<drmVersion> version(drmGetVersion(fd)); |
|
m_isI915 = strstr(version->name, "i915"); |
|
m_isNVidia = strstr(version->name, "nvidia-drm"); |
|
m_isVirtualMachine = strstr(version->name, "virtio") || strstr(version->name, "qxl") |
|
|| strstr(version->name, "vmwgfx") || strstr(version->name, "vboxvideo"); |
|
|
|
// Reopen the drm node to create a new GEM handle namespace. |
|
m_gbmFd = FileDescriptor{open(devNode.toLocal8Bit(), O_RDWR | O_CLOEXEC)}; |
|
if (m_gbmFd.isValid()) { |
|
drm_magic_t magic; |
|
drmGetMagic(m_gbmFd.get(), &magic); |
|
drmAuthMagic(m_fd, magic); |
|
m_gbmDevice = gbm_create_device(m_gbmFd.get()); |
|
if (!m_gbmDevice) { |
|
qCCritical(KWIN_DRM) << "gbm_create_device() failed"; |
|
} else { |
|
m_allocator = std::make_unique<GbmGraphicsBufferAllocator>(m_gbmDevice); |
|
} |
|
} |
|
|
|
m_socketNotifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read); |
|
connect(m_socketNotifier.get(), &QSocketNotifier::activated, this, &DrmGpu::dispatchEvents); |
|
|
|
initDrmResources(); |
|
|
|
if (m_atomicModeSetting == false) { |
|
// only supported with legacy |
|
m_asyncPageflipSupported = drmGetCap(fd, DRM_CAP_ASYNC_PAGE_FLIP, &capability) == 0 && capability == 1; |
|
} |
|
} |
|
|
|
DrmGpu::~DrmGpu() |
|
{ |
|
removeOutputs(); |
|
m_eglDisplay.reset(); |
|
m_crtcs.clear(); |
|
m_connectors.clear(); |
|
m_planes.clear(); |
|
m_socketNotifier.reset(); |
|
m_allocator.reset(); |
|
if (m_gbmDevice) { |
|
gbm_device_destroy(m_gbmDevice); |
|
} |
|
m_gbmFd = FileDescriptor{}; |
|
m_platform->session()->closeRestricted(m_fd); |
|
} |
|
|
|
FileDescriptor DrmGpu::createNonMasterFd() const |
|
{ |
|
char *path = drmGetDeviceNameFromFd2(m_fd); |
|
FileDescriptor fd{open(path, O_RDWR | O_CLOEXEC)}; |
|
if (!fd.isValid()) { |
|
qCWarning(KWIN_DRM) << "Could not open DRM fd for leasing!" << strerror(errno); |
|
} else { |
|
if (drmIsMaster(fd.get())) { |
|
if (drmDropMaster(fd.get()) != 0) { |
|
qCWarning(KWIN_DRM) << "Could not create a non-master DRM fd for leasing!" << strerror(errno); |
|
return FileDescriptor{}; |
|
} |
|
} |
|
} |
|
return fd; |
|
} |
|
|
|
clockid_t DrmGpu::presentationClock() const |
|
{ |
|
return m_presentationClock; |
|
} |
|
|
|
void DrmGpu::initDrmResources() |
|
{ |
|
// try atomic mode setting |
|
bool isEnvVarSet = false; |
|
const bool supportsVmCursorHotspot = drmSetClientCap(m_fd, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT, 1) == 0; |
|
const bool noAMS = qEnvironmentVariableIntValue("KWIN_DRM_NO_AMS", &isEnvVarSet) != 0 && isEnvVarSet; |
|
if (m_isVirtualMachine && !supportsVmCursorHotspot && !isEnvVarSet) { |
|
qCWarning(KWIN_DRM, "Atomic Mode Setting disabled on GPU %s because of cursor offset issues in virtual machines", qPrintable(m_devNode)); |
|
} else if (noAMS) { |
|
qCWarning(KWIN_DRM) << "Atomic Mode Setting requested off via environment variable. Using legacy mode on GPU" << m_devNode; |
|
} else if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) != 0) { |
|
qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << m_devNode; |
|
} else { |
|
DrmUniquePtr<drmModePlaneRes> planeResources(drmModeGetPlaneResources(m_fd)); |
|
if (planeResources) { |
|
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) { |
|
DrmUniquePtr<drmModePlane> kplane(drmModeGetPlane(m_fd, planeResources->planes[i])); |
|
auto plane = std::make_unique<DrmPlane>(this, kplane->plane_id); |
|
if (plane->init()) { |
|
m_allObjects << plane.get(); |
|
m_planes.push_back(std::move(plane)); |
|
} |
|
} |
|
if (m_planes.empty()) { |
|
qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << m_devNode; |
|
} |
|
} else { |
|
qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode; |
|
} |
|
} |
|
m_atomicModeSetting = !m_planes.empty(); |
|
|
|
DrmUniquePtr<drmModeRes> resources(drmModeGetResources(m_fd)); |
|
if (!resources) { |
|
qCCritical(KWIN_DRM) << "drmModeGetResources for getting CRTCs failed on GPU" << m_devNode; |
|
return; |
|
} |
|
QList<DrmPlane *> assignedPlanes; |
|
for (int i = 0; i < resources->count_crtcs; ++i) { |
|
uint32_t crtcId = resources->crtcs[i]; |
|
QList<DrmPlane *> primaryCandidates; |
|
QList<DrmPlane *> cursorCandidates; |
|
for (const auto &plane : m_planes) { |
|
if (plane->isCrtcSupported(i) && !assignedPlanes.contains(plane.get())) { |
|
if (plane->type.enumValue() == DrmPlane::TypeIndex::Primary) { |
|
primaryCandidates.push_back(plane.get()); |
|
} else if (plane->type.enumValue() == DrmPlane::TypeIndex::Cursor) { |
|
cursorCandidates.push_back(plane.get()); |
|
} |
|
} |
|
} |
|
if (m_atomicModeSetting && primaryCandidates.empty()) { |
|
qCWarning(KWIN_DRM) << "Could not find a suitable primary plane for crtc" << resources->crtcs[i]; |
|
continue; |
|
} |
|
const auto findBestPlane = [crtcId](const QList<DrmPlane *> &list) { |
|
// if the plane is already used with this crtc, prefer it |
|
const auto connected = std::find_if(list.begin(), list.end(), [crtcId](DrmPlane *plane) { |
|
return plane->crtcId.value() == crtcId; |
|
}); |
|
if (connected != list.end()) { |
|
return *connected; |
|
} |
|
// don't take away planes from other crtcs. The kernel currently rejects such commits |
|
const auto notconnected = std::find_if(list.begin(), list.end(), [](DrmPlane *plane) { |
|
return plane->crtcId.value() == 0; |
|
}); |
|
if (notconnected != list.end()) { |
|
return *notconnected; |
|
} |
|
return list.empty() ? nullptr : list.front(); |
|
}; |
|
DrmPlane *primary = findBestPlane(primaryCandidates); |
|
DrmPlane *cursor = findBestPlane(cursorCandidates); |
|
assignedPlanes.push_back(primary); |
|
if (cursor) { |
|
assignedPlanes.push_back(cursor); |
|
} |
|
auto crtc = std::make_unique<DrmCrtc>(this, crtcId, i, primary, cursor); |
|
if (!crtc->init()) { |
|
continue; |
|
} |
|
m_allObjects << crtc.get(); |
|
m_crtcs.push_back(std::move(crtc)); |
|
} |
|
} |
|
|
|
bool DrmGpu::updateOutputs() |
|
{ |
|
if (!m_isActive) { |
|
return false; |
|
} |
|
waitIdle(); |
|
DrmUniquePtr<drmModeRes> resources(drmModeGetResources(m_fd)); |
|
if (!resources) { |
|
qCWarning(KWIN_DRM) << "drmModeGetResources failed"; |
|
return false; |
|
} |
|
|
|
// In principle these things are supposed to be detected through the wayland protocol. |
|
// In practice SteamVR doesn't always behave correctly |
|
DrmUniquePtr<drmModeLesseeListRes> lessees{drmModeListLessees(m_fd)}; |
|
for (const auto &output : std::as_const(m_drmOutputs)) { |
|
if (output->lease()) { |
|
bool leaseActive = false; |
|
for (uint i = 0; i < lessees->count; i++) { |
|
if (lessees->lessees[i] == output->lease()->lesseeId()) { |
|
leaseActive = true; |
|
break; |
|
} |
|
} |
|
if (!leaseActive) { |
|
Q_EMIT output->lease()->revokeRequested(); |
|
} |
|
} |
|
} |
|
|
|
// check for added and removed connectors |
|
QList<DrmConnector *> existing; |
|
QList<DrmOutput *> addedOutputs; |
|
for (int i = 0; i < resources->count_connectors; ++i) { |
|
const uint32_t currentConnector = resources->connectors[i]; |
|
const auto it = std::find_if(m_connectors.begin(), m_connectors.end(), [currentConnector](const auto &connector) { |
|
return connector->id() == currentConnector; |
|
}); |
|
if (it == m_connectors.end()) { |
|
auto conn = std::make_shared<DrmConnector>(this, currentConnector); |
|
if (!conn->init()) { |
|
continue; |
|
} |
|
existing.push_back(conn.get()); |
|
m_allObjects.push_back(conn.get()); |
|
m_connectors.push_back(std::move(conn)); |
|
} else { |
|
(*it)->updateProperties(); |
|
existing.push_back(it->get()); |
|
} |
|
} |
|
for (auto it = m_connectors.begin(); it != m_connectors.end();) { |
|
DrmConnector *conn = it->get(); |
|
const auto output = findOutput(conn->id()); |
|
const bool stillExists = existing.contains(conn); |
|
if (!stillExists || !conn->isConnected()) { |
|
if (output) { |
|
removeOutput(output); |
|
} |
|
} else if (!output) { |
|
qCDebug(KWIN_DRM, "New %soutput on GPU %s: %s", conn->isNonDesktop() ? "non-desktop " : "", qPrintable(m_devNode), qPrintable(conn->modelName())); |
|
const auto pipeline = conn->pipeline(); |
|
m_pipelines << pipeline; |
|
auto output = new DrmOutput(*it); |
|
m_drmOutputs << output; |
|
addedOutputs << output; |
|
Q_EMIT outputAdded(output); |
|
pipeline->setLayers(m_platform->renderBackend()->createPrimaryLayer(pipeline), m_platform->renderBackend()->createCursorLayer(pipeline)); |
|
pipeline->setActive(!conn->isNonDesktop()); |
|
pipeline->applyPendingChanges(); |
|
} |
|
if (stillExists) { |
|
it++; |
|
} else { |
|
m_allObjects.removeOne(it->get()); |
|
it = m_connectors.erase(it); |
|
} |
|
} |
|
|
|
// update crtc properties |
|
for (const auto &crtc : std::as_const(m_crtcs)) { |
|
crtc->updateProperties(); |
|
} |
|
// update plane properties |
|
for (const auto &plane : std::as_const(m_planes)) { |
|
plane->updateProperties(); |
|
} |
|
DrmPipeline::Error err = testPendingConfiguration(); |
|
if (err == DrmPipeline::Error::None) { |
|
for (const auto &pipeline : std::as_const(m_pipelines)) { |
|
pipeline->applyPendingChanges(); |
|
if (pipeline->output() && !pipeline->crtc()) { |
|
pipeline->setEnable(false); |
|
pipeline->output()->updateEnabled(false); |
|
} |
|
} |
|
} else if (err == DrmPipeline::Error::NoPermission) { |
|
for (const auto &pipeline : std::as_const(m_pipelines)) { |
|
pipeline->revertPendingChanges(); |
|
} |
|
for (const auto &output : std::as_const(addedOutputs)) { |
|
removeOutput(output); |
|
const auto it = std::find_if(m_connectors.begin(), m_connectors.end(), [output](const auto &conn) { |
|
return conn.get() == output->connector(); |
|
}); |
|
Q_ASSERT(it != m_connectors.end()); |
|
m_allObjects.removeOne(it->get()); |
|
m_connectors.erase(it); |
|
} |
|
} else { |
|
qCWarning(KWIN_DRM, "Failed to find a working setup for new outputs!"); |
|
for (const auto &pipeline : std::as_const(m_pipelines)) { |
|
pipeline->revertPendingChanges(); |
|
} |
|
for (const auto &output : std::as_const(addedOutputs)) { |
|
output->updateEnabled(false); |
|
output->pipeline()->setEnable(false); |
|
output->pipeline()->applyPendingChanges(); |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
void DrmGpu::removeOutputs() |
|
{ |
|
waitIdle(); |
|
|
|
const auto outputs = m_drmOutputs; |
|
for (const auto &output : outputs) { |
|
removeOutput(output); |
|
} |
|
const auto virtualOutputs = m_virtualOutputs; |
|
for (const auto &output : virtualOutputs) { |
|
removeVirtualOutput(output); |
|
} |
|
} |
|
|
|
DrmPipeline::Error DrmGpu::checkCrtcAssignment(QList<DrmConnector *> connectors, const QList<DrmCrtc *> &crtcs) |
|
{ |
|
if (connectors.isEmpty() || crtcs.isEmpty()) { |
|
if (m_pipelines.isEmpty()) { |
|
// nothing to do |
|
return DrmPipeline::Error::None; |
|
} |
|
// remaining connectors can't be powered |
|
for (const auto &conn : std::as_const(connectors)) { |
|
qCWarning(KWIN_DRM) << "disabling connector" << conn->modelName() << "without a crtc"; |
|
conn->pipeline()->setCrtc(nullptr); |
|
} |
|
return testPipelines(); |
|
} |
|
auto connector = connectors.takeFirst(); |
|
auto pipeline = connector->pipeline(); |
|
if (!pipeline->enabled() || !connector->isConnected()) { |
|
// disabled pipelines don't need CRTCs |
|
pipeline->setCrtc(nullptr); |
|
return checkCrtcAssignment(connectors, crtcs); |
|
} |
|
DrmCrtc *currentCrtc = nullptr; |
|
if (m_atomicModeSetting) { |
|
// try the crtc that this connector is already connected to first |
|
const uint32_t id = connector->crtcId.value(); |
|
auto it = std::find_if(crtcs.begin(), crtcs.end(), [id](const auto &crtc) { |
|
return id == crtc->id(); |
|
}); |
|
if (it != crtcs.end()) { |
|
currentCrtc = *it; |
|
auto crtcsLeft = crtcs; |
|
crtcsLeft.removeOne(currentCrtc); |
|
pipeline->setCrtc(currentCrtc); |
|
do { |
|
DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft); |
|
if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) { |
|
return err; |
|
} |
|
} while (pipeline->pruneModifier()); |
|
} |
|
} |
|
for (const auto &crtc : std::as_const(crtcs)) { |
|
if (connector->isCrtcSupported(crtc) && crtc != currentCrtc) { |
|
auto crtcsLeft = crtcs; |
|
crtcsLeft.removeOne(crtc); |
|
pipeline->setCrtc(crtc); |
|
do { |
|
DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft); |
|
if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) { |
|
return err; |
|
} |
|
} while (pipeline->pruneModifier()); |
|
} |
|
} |
|
return DrmPipeline::Error::InvalidArguments; |
|
} |
|
|
|
DrmPipeline::Error DrmGpu::testPendingConfiguration() |
|
{ |
|
QList<DrmConnector *> connectors; |
|
QList<DrmCrtc *> crtcs; |
|
// only change resources that aren't currently leased away |
|
for (const auto &conn : m_connectors) { |
|
bool isLeased = std::any_of(m_drmOutputs.cbegin(), m_drmOutputs.cend(), [&conn](const auto output) { |
|
return output->lease() && output->pipeline()->connector() == conn.get(); |
|
}); |
|
if (!isLeased) { |
|
connectors.push_back(conn.get()); |
|
} |
|
} |
|
for (const auto &crtc : m_crtcs) { |
|
bool isLeased = std::any_of(m_drmOutputs.cbegin(), m_drmOutputs.cend(), [&crtc](const auto output) { |
|
return output->lease() && output->pipeline()->crtc() == crtc.get(); |
|
}); |
|
if (!isLeased) { |
|
crtcs.push_back(crtc.get()); |
|
} |
|
} |
|
if (m_atomicModeSetting) { |
|
// sort outputs by being already connected (to any CRTC) so that already working outputs get preferred |
|
std::sort(connectors.begin(), connectors.end(), [](auto c1, auto c2) { |
|
return c1->crtcId.value() > c2->crtcId.value(); |
|
}); |
|
} |
|
return checkCrtcAssignment(connectors, crtcs); |
|
} |
|
|
|
DrmPipeline::Error DrmGpu::testPipelines() |
|
{ |
|
QList<DrmPipeline *> inactivePipelines; |
|
std::copy_if(m_pipelines.constBegin(), m_pipelines.constEnd(), std::back_inserter(inactivePipelines), [](const auto pipeline) { |
|
return pipeline->enabled() && !pipeline->active(); |
|
}); |
|
DrmPipeline::Error test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::TestAllowModeset, unusedObjects()); |
|
if (!inactivePipelines.isEmpty() && test == DrmPipeline::Error::None) { |
|
// ensure that pipelines that are set as enabled but currently inactive |
|
// still work when they need to be set active again |
|
for (const auto pipeline : std::as_const(inactivePipelines)) { |
|
pipeline->setActive(true); |
|
} |
|
test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::TestAllowModeset, unusedObjects()); |
|
for (const auto pipeline : std::as_const(inactivePipelines)) { |
|
pipeline->setActive(false); |
|
} |
|
} |
|
return test; |
|
} |
|
|
|
DrmOutput *DrmGpu::findOutput(quint32 connector) |
|
{ |
|
auto it = std::find_if(m_drmOutputs.constBegin(), m_drmOutputs.constEnd(), [connector](DrmOutput *o) { |
|
return o->connector()->id() == connector; |
|
}); |
|
if (it != m_drmOutputs.constEnd()) { |
|
return *it; |
|
} |
|
return nullptr; |
|
} |
|
|
|
void DrmGpu::waitIdle() |
|
{ |
|
m_socketNotifier->setEnabled(false); |
|
while (true) { |
|
const bool idle = std::all_of(m_drmOutputs.constBegin(), m_drmOutputs.constEnd(), [](DrmOutput *output) { |
|
return !output->pipeline()->pageflipsPending(); |
|
}); |
|
if (idle) { |
|
break; |
|
} |
|
pollfd pfds[1]; |
|
pfds[0].fd = m_fd; |
|
pfds[0].events = POLLIN; |
|
|
|
const int ready = poll(pfds, 1, 30000); |
|
if (ready < 0) { |
|
if (errno != EINTR) { |
|
qCWarning(KWIN_DRM) << Q_FUNC_INFO << "poll() failed:" << strerror(errno); |
|
break; |
|
} |
|
} else if (ready == 0) { |
|
qCWarning(KWIN_DRM) << "No drm events for gpu" << m_devNode << "within last 30 seconds"; |
|
break; |
|
} else { |
|
dispatchEvents(); |
|
} |
|
}; |
|
m_socketNotifier->setEnabled(true); |
|
} |
|
|
|
static std::chrono::nanoseconds convertTimestamp(const timespec ×tamp) |
|
{ |
|
return std::chrono::seconds(timestamp.tv_sec) + std::chrono::nanoseconds(timestamp.tv_nsec); |
|
} |
|
|
|
static std::chrono::nanoseconds convertTimestamp(clockid_t sourceClock, clockid_t targetClock, |
|
const timespec ×tamp) |
|
{ |
|
if (sourceClock == targetClock) { |
|
return convertTimestamp(timestamp); |
|
} |
|
|
|
timespec sourceCurrentTime = {}; |
|
timespec targetCurrentTime = {}; |
|
|
|
clock_gettime(sourceClock, &sourceCurrentTime); |
|
clock_gettime(targetClock, &targetCurrentTime); |
|
|
|
const auto delta = convertTimestamp(sourceCurrentTime) - convertTimestamp(timestamp); |
|
return convertTimestamp(targetCurrentTime) - delta; |
|
} |
|
|
|
void DrmGpu::pageFlipHandler(int fd, unsigned int sequence, unsigned int sec, unsigned int usec, unsigned int crtc_id, void *user_data) |
|
{ |
|
const auto commit = static_cast<DrmCommit *>(user_data); |
|
const auto gpu = commit->gpu(); |
|
|
|
// The static_cast<> here are for a 32-bit environment where |
|
// sizeof(time_t) == sizeof(unsigned int) == 4 . Putting @p sec |
|
// into a time_t cuts off the most-significant bit (after the |
|
// year 2038), similarly long can't hold all the bits of an |
|
// unsigned multiplication. |
|
std::chrono::nanoseconds timestamp = convertTimestamp(gpu->presentationClock(), CLOCK_MONOTONIC, |
|
{static_cast<time_t>(sec), static_cast<long>(usec * 1000)}); |
|
if (timestamp == std::chrono::nanoseconds::zero()) { |
|
qCDebug(KWIN_DRM, "Got invalid timestamp (sec: %u, usec: %u) on gpu %s", |
|
sec, usec, qPrintable(gpu->devNode())); |
|
timestamp = std::chrono::steady_clock::now().time_since_epoch(); |
|
} |
|
commit->pageFlipped(timestamp); |
|
delete commit; |
|
} |
|
|
|
void DrmGpu::dispatchEvents() |
|
{ |
|
drmEventContext context = {}; |
|
context.version = 3; |
|
context.page_flip_handler2 = pageFlipHandler; |
|
drmHandleEvent(m_fd, &context); |
|
} |
|
|
|
void DrmGpu::removeOutput(DrmOutput *output) |
|
{ |
|
qCDebug(KWIN_DRM) << "Removing output" << output; |
|
m_pipelines.removeOne(output->pipeline()); |
|
output->pipeline()->setLayers(nullptr, nullptr); |
|
m_drmOutputs.removeOne(output); |
|
Q_EMIT outputRemoved(output); |
|
output->unref(); |
|
} |
|
|
|
DrmBackend *DrmGpu::platform() const |
|
{ |
|
return m_platform; |
|
} |
|
|
|
const QList<DrmPipeline *> DrmGpu::pipelines() const |
|
{ |
|
return m_pipelines; |
|
} |
|
|
|
DrmVirtualOutput *DrmGpu::createVirtualOutput(const QString &name, const QSize &size, double scale) |
|
{ |
|
auto output = new DrmVirtualOutput(name, this, size, scale); |
|
m_virtualOutputs << output; |
|
Q_EMIT outputAdded(output); |
|
return output; |
|
} |
|
|
|
void DrmGpu::removeVirtualOutput(DrmVirtualOutput *output) |
|
{ |
|
if (m_virtualOutputs.removeOne(output)) { |
|
Q_EMIT outputRemoved(output); |
|
output->unref(); |
|
} |
|
} |
|
|
|
std::unique_ptr<DrmLease> DrmGpu::leaseOutputs(const QList<DrmOutput *> &outputs) |
|
{ |
|
QList<uint32_t> objects; |
|
for (DrmOutput *output : outputs) { |
|
if (output->lease() || !output->addLeaseObjects(objects)) { |
|
return nullptr; |
|
} |
|
} |
|
|
|
uint32_t lesseeId; |
|
FileDescriptor fd{drmModeCreateLease(m_fd, objects.constData(), objects.count(), 0, &lesseeId)}; |
|
if (!fd.isValid()) { |
|
qCWarning(KWIN_DRM) << "Could not create DRM lease!" << strerror(errno); |
|
qCWarning(KWIN_DRM) << "Tried to lease the following" << objects.count() << "resources:"; |
|
for (const auto &res : std::as_const(objects)) { |
|
qCWarning(KWIN_DRM) << res; |
|
} |
|
return nullptr; |
|
} else { |
|
qCDebug(KWIN_DRM) << "Created lease for" << objects.count() << "resources:"; |
|
for (const auto &res : std::as_const(objects)) { |
|
qCDebug(KWIN_DRM) << res; |
|
} |
|
return std::make_unique<DrmLease>(this, std::move(fd), lesseeId, outputs); |
|
} |
|
} |
|
|
|
QList<DrmVirtualOutput *> DrmGpu::virtualOutputs() const |
|
{ |
|
return m_virtualOutputs; |
|
} |
|
|
|
QList<DrmOutput *> DrmGpu::drmOutputs() const |
|
{ |
|
return m_drmOutputs; |
|
} |
|
|
|
int DrmGpu::fd() const |
|
{ |
|
return m_fd; |
|
} |
|
|
|
dev_t DrmGpu::deviceId() const |
|
{ |
|
return m_deviceId; |
|
} |
|
|
|
bool DrmGpu::atomicModeSetting() const |
|
{ |
|
return m_atomicModeSetting; |
|
} |
|
|
|
QString DrmGpu::devNode() const |
|
{ |
|
return m_devNode; |
|
} |
|
|
|
gbm_device *DrmGpu::gbmDevice() const |
|
{ |
|
return m_gbmDevice; |
|
} |
|
|
|
EglDisplay *DrmGpu::eglDisplay() const |
|
{ |
|
return m_eglDisplay.get(); |
|
} |
|
|
|
void DrmGpu::setEglDisplay(std::unique_ptr<EglDisplay> &&display) |
|
{ |
|
m_eglDisplay = std::move(display); |
|
} |
|
|
|
bool DrmGpu::addFB2ModifiersSupported() const |
|
{ |
|
return m_addFB2ModifiersSupported; |
|
} |
|
|
|
bool DrmGpu::asyncPageflipSupported() const |
|
{ |
|
return m_asyncPageflipSupported; |
|
} |
|
|
|
bool DrmGpu::isI915() const |
|
{ |
|
return m_isI915; |
|
} |
|
|
|
bool DrmGpu::isNVidia() const |
|
{ |
|
return m_isNVidia; |
|
} |
|
|
|
bool DrmGpu::isRemoved() const |
|
{ |
|
return m_isRemoved; |
|
} |
|
|
|
void DrmGpu::setRemoved() |
|
{ |
|
m_isRemoved = true; |
|
} |
|
|
|
void DrmGpu::setActive(bool active) |
|
{ |
|
if (m_isActive != active) { |
|
m_isActive = active; |
|
if (active) { |
|
for (const auto &output : std::as_const(m_drmOutputs)) { |
|
output->renderLoop()->uninhibit(); |
|
} |
|
// while the session was inactive, the output list may have changed |
|
m_platform->updateOutputs(); |
|
for (const auto &output : std::as_const(m_drmOutputs)) { |
|
// force a modeset with legacy, we can't reliably know if one is needed |
|
if (!atomicModeSetting()) { |
|
output->pipeline()->forceLegacyModeset(); |
|
} |
|
} |
|
} else { |
|
for (const auto &output : std::as_const(m_drmOutputs)) { |
|
output->renderLoop()->inhibit(); |
|
} |
|
} |
|
Q_EMIT activeChanged(active); |
|
} |
|
} |
|
|
|
bool DrmGpu::isActive() const |
|
{ |
|
return m_isActive; |
|
} |
|
|
|
bool DrmGpu::needsModeset() const |
|
{ |
|
return std::any_of(m_pipelines.constBegin(), m_pipelines.constEnd(), [](const auto &pipeline) { |
|
return pipeline->needsModeset(); |
|
}); |
|
} |
|
|
|
bool DrmGpu::maybeModeset() |
|
{ |
|
auto pipelines = m_pipelines; |
|
for (const auto &output : std::as_const(m_drmOutputs)) { |
|
if (output->lease()) { |
|
pipelines.removeOne(output->pipeline()); |
|
} |
|
} |
|
bool presentPendingForAll = std::all_of(pipelines.constBegin(), pipelines.constEnd(), [](const auto &pipeline) { |
|
return pipeline->modesetPresentPending() || !pipeline->activePending(); |
|
}); |
|
if (!presentPendingForAll) { |
|
// commit only once all pipelines are ready for presentation |
|
return true; |
|
} |
|
// make sure there's no pending pageflips |
|
waitIdle(); |
|
const DrmPipeline::Error err = DrmPipeline::commitPipelines(pipelines, DrmPipeline::CommitMode::CommitModeset, unusedObjects()); |
|
for (DrmPipeline *pipeline : std::as_const(pipelines)) { |
|
if (pipeline->modesetPresentPending()) { |
|
pipeline->resetModesetPresentPending(); |
|
if (err != DrmPipeline::Error::None) { |
|
pipeline->output()->frameFailed(); |
|
} |
|
} |
|
} |
|
if (err == DrmPipeline::Error::None) { |
|
return true; |
|
} else { |
|
if (err != DrmPipeline::Error::FramePending) { |
|
QTimer::singleShot(0, m_platform, &DrmBackend::updateOutputs); |
|
} |
|
return false; |
|
} |
|
} |
|
|
|
QList<DrmObject *> DrmGpu::unusedObjects() const |
|
{ |
|
if (!m_atomicModeSetting) { |
|
return {}; |
|
} |
|
QList<DrmObject *> ret = m_allObjects; |
|
for (const auto &pipeline : m_pipelines) { |
|
ret.removeOne(pipeline->connector()); |
|
if (pipeline->crtc()) { |
|
ret.removeOne(pipeline->crtc()); |
|
ret.removeOne(pipeline->crtc()->primaryPlane()); |
|
ret.removeOne(pipeline->crtc()->cursorPlane()); |
|
} |
|
} |
|
return ret; |
|
} |
|
|
|
QSize DrmGpu::cursorSize() const |
|
{ |
|
return m_cursorSize; |
|
} |
|
|
|
void DrmGpu::releaseBuffers() |
|
{ |
|
for (const auto &plane : std::as_const(m_planes)) { |
|
plane->releaseCurrentBuffer(); |
|
} |
|
for (const auto &crtc : std::as_const(m_crtcs)) { |
|
crtc->releaseCurrentBuffer(); |
|
} |
|
for (const auto &pipeline : std::as_const(m_pipelines)) { |
|
pipeline->primaryLayer()->releaseBuffers(); |
|
pipeline->cursorLayer()->releaseBuffers(); |
|
} |
|
for (const auto &output : std::as_const(m_virtualOutputs)) { |
|
output->primaryLayer()->releaseBuffers(); |
|
} |
|
} |
|
|
|
void DrmGpu::recreateSurfaces() |
|
{ |
|
for (const auto &pipeline : std::as_const(m_pipelines)) { |
|
pipeline->setLayers(m_platform->renderBackend()->createPrimaryLayer(pipeline), m_platform->renderBackend()->createCursorLayer(pipeline)); |
|
pipeline->applyPendingChanges(); |
|
} |
|
for (const auto &output : std::as_const(m_virtualOutputs)) { |
|
output->recreateSurface(); |
|
} |
|
} |
|
|
|
GraphicsBufferAllocator *DrmGpu::graphicsBufferAllocator() const |
|
{ |
|
return m_allocator.get(); |
|
} |
|
|
|
std::shared_ptr<DrmFramebuffer> DrmGpu::importBuffer(GraphicsBuffer *buffer) |
|
{ |
|
const DmaBufAttributes *attributes = buffer->dmabufAttributes(); |
|
if (Q_UNLIKELY(!attributes)) { |
|
return nullptr; |
|
} |
|
|
|
uint32_t handles[] = {0, 0, 0, 0}; |
|
auto cleanup = qScopeGuard([this, &handles]() { |
|
for (int i = 0; i < 4; ++i) { |
|
if (handles[i] == 0) { |
|
continue; |
|
} |
|
bool closed = false; |
|
for (int j = 0; j < i; ++j) { |
|
if (handles[i] == handles[j]) { |
|
closed = true; |
|
break; |
|
} |
|
} |
|
if (closed) { |
|
continue; |
|
} |
|
drmCloseBufferHandle(m_fd, handles[i]); |
|
} |
|
}); |
|
for (int i = 0; i < attributes->planeCount; ++i) { |
|
if (drmPrimeFDToHandle(m_fd, attributes->fd[i].get(), &handles[i]) != 0) { |
|
qCWarning(KWIN_DRM) << "drmPrimeFDToHandle() failed"; |
|
return nullptr; |
|
} |
|
} |
|
|
|
uint32_t framebufferId = 0; |
|
int ret; |
|
if (addFB2ModifiersSupported() && attributes->modifier != DRM_FORMAT_MOD_INVALID) { |
|
uint64_t modifier[4] = {0, 0, 0, 0}; |
|
for (int i = 0; i < attributes->planeCount; ++i) { |
|
modifier[i] = attributes->modifier; |
|
} |
|
ret = drmModeAddFB2WithModifiers(m_fd, |
|
attributes->width, |
|
attributes->height, |
|
attributes->format, |
|
handles, |
|
attributes->pitch.data(), |
|
attributes->offset.data(), |
|
modifier, |
|
&framebufferId, |
|
DRM_MODE_FB_MODIFIERS); |
|
} else { |
|
ret = drmModeAddFB2(m_fd, |
|
attributes->width, |
|
attributes->height, |
|
attributes->format, |
|
handles, |
|
attributes->pitch.data(), |
|
attributes->offset.data(), |
|
&framebufferId, |
|
0); |
|
if (ret == EOPNOTSUPP && attributes->planeCount == 1) { |
|
ret = drmModeAddFB(m_fd, |
|
attributes->width, |
|
attributes->height, |
|
24, 32, |
|
attributes->pitch[0], |
|
handles[0], |
|
&framebufferId); |
|
} |
|
} |
|
|
|
if (ret != 0) { |
|
return nullptr; |
|
} |
|
|
|
return std::make_shared<DrmFramebuffer>(this, framebufferId, buffer); |
|
} |
|
|
|
DrmLease::DrmLease(DrmGpu *gpu, FileDescriptor &&fd, uint32_t lesseeId, const QList<DrmOutput *> &outputs) |
|
: m_gpu(gpu) |
|
, m_fd(std::move(fd)) |
|
, m_lesseeId(lesseeId) |
|
, m_outputs(outputs) |
|
{ |
|
for (const auto output : m_outputs) { |
|
output->leased(this); |
|
} |
|
} |
|
|
|
DrmLease::~DrmLease() |
|
{ |
|
qCDebug(KWIN_DRM, "Revoking lease with leaseID %d", m_lesseeId); |
|
drmModeRevokeLease(m_gpu->fd(), m_lesseeId); |
|
for (const auto &output : m_outputs) { |
|
output->leaseEnded(); |
|
} |
|
} |
|
|
|
FileDescriptor &DrmLease::fd() |
|
{ |
|
return m_fd; |
|
} |
|
|
|
uint32_t DrmLease::lesseeId() const |
|
{ |
|
return m_lesseeId; |
|
} |
|
} |
|
|
|
#include "moc_drm_gpu.cpp"
|
|
|