Commit 30464e5c authored by Xaver Hugl's avatar Xaver Hugl
Browse files

Implement direct scanout for the gbm backend

parent 66f87138
......@@ -128,4 +128,18 @@ RenderLoop *AbstractOutput::renderLoop() const
return nullptr;
}
void AbstractOutput::inhibitDirectScanout()
{
m_directScanoutCount++;
}
void AbstractOutput::uninhibitDirectScanout()
{
m_directScanoutCount--;
}
bool AbstractOutput::directScanoutInhibited() const
{
return m_directScanoutCount;
}
} // namespace KWin
......@@ -190,6 +190,11 @@ public:
*/
virtual RenderLoop *renderLoop() const;
void inhibitDirectScanout();
void uninhibitDirectScanout();
bool directScanoutInhibited() const;
Q_SIGNALS:
/**
* This signal is emitted when the geometry of this output has changed.
......@@ -202,6 +207,7 @@ Q_SIGNALS:
private:
Q_DISABLE_COPY(AbstractOutput)
int m_directScanoutCount = 0;
};
} // namespace KWin
......
......@@ -374,4 +374,19 @@ QMatrix4x4 AbstractWaylandOutput::logicalToNativeMatrix(const QRect &rect, qreal
return matrix;
}
void AbstractWaylandOutput::recordingStarted()
{
m_recorders++;
}
void AbstractWaylandOutput::recordingStopped()
{
m_recorders--;
}
bool AbstractWaylandOutput::isBeingRecorded()
{
return m_recorders;
}
}
......@@ -121,6 +121,11 @@ public:
*/
static QMatrix4x4 logicalToNativeMatrix(const QRect &rect, qreal scale, Transform transform);
void recordingStarted();
void recordingStopped();
bool isBeingRecorded();
Q_SIGNALS:
void modeChanged();
void outputChange(const QRegion &damagedRegion);
......@@ -174,6 +179,7 @@ private:
QString m_name;
bool m_internal = false;
int m_recorders = 0;
};
}
......
......@@ -1507,6 +1507,17 @@ QStringList EffectsHandlerImpl::activeEffects() const
return ret;
}
bool EffectsHandlerImpl::blocksDirectScanout() const
{
for(QVector< KWin::EffectPair >::const_iterator it = loaded_effects.constBegin(),
end = loaded_effects.constEnd(); it != end; ++it) {
if (it->second->isActive() && it->second->blocksDirectScanout()) {
return true;
}
}
return false;
}
KWaylandServer::Display *EffectsHandlerImpl::waylandDisplay() const
{
if (waylandServer()) {
......
......@@ -203,6 +203,11 @@ public:
QList<EffectWindow*> elevatedWindows() const;
QStringList activeEffects() const;
/**
* @returns whether or not any effect is currently active where KWin should not use direct scanout
*/
bool blocksDirectScanout() const;
/**
* @returns Whether we are currently in a desktop rendering process triggered by paintDesktop hook
*/
......
......@@ -516,5 +516,10 @@ bool ContrastEffect::isActive() const
return !effects->isScreenLocked();
}
bool ContrastEffect::blocksDirectScanout() const
{
return false;
}
} // namespace KWin
......@@ -51,6 +51,8 @@ public:
bool eventFilter(QObject *watched, QEvent *event) override;
bool blocksDirectScanout() const override;
public Q_SLOTS:
void slotWindowAdded(KWin::EffectWindow *w);
void slotWindowDeleted(KWin::EffectWindow *w);
......
......@@ -814,5 +814,10 @@ bool BlurEffect::isActive() const
return !effects->isScreenLocked();
}
bool BlurEffect::blocksDirectScanout() const
{
return false;
}
} // namespace KWin
......@@ -54,6 +54,8 @@ public:
bool eventFilter(QObject *watched, QEvent *event) override;
bool blocksDirectScanout() const override;
public Q_SLOTS:
void slotWindowAdded(KWin::EffectWindow *w);
void slotWindowDeleted(KWin::EffectWindow *w);
......
......@@ -705,6 +705,11 @@ bool Effect::perform(Feature feature, const QVariantList &arguments)
return false;
}
bool Effect::blocksDirectScanout() const
{
return true;
}
//****************************************
// EffectFactory
//****************************************
......
......@@ -664,6 +664,11 @@ public:
static void setPositionTransformations(WindowPaintData& data, QRect& region, EffectWindow* w,
const QRect& r, Qt::AspectRatioMode aspect);
/**
* overwrite this method to return false if your effect does not need to be drawn over opaque fullscreen windows
*/
virtual bool blocksDirectScanout() const;
public Q_SLOTS:
virtual bool borderActivated(ElectricBorder border);
......
......@@ -64,6 +64,13 @@ OverlayWindow* OpenGLBackend::overlayWindow() const
return nullptr;
}
bool OpenGLBackend::scanout(int screenId, KWaylandServer::SurfaceInterface *surface)
{
Q_UNUSED(screenId)
Q_UNUSED(surface)
return false;
}
void OpenGLBackend::copyPixels(const QRegion &region)
{
const int height = screens()->size().height();
......@@ -89,4 +96,11 @@ void OpenGLBackend::aboutToStartPainting(int screenId, const QRegion &damage)
Q_UNUSED(damage)
}
bool OpenGLBackend::directScanoutAllowed(int screen) const
{
Q_UNUSED(screen);
return false;
}
}
......@@ -14,6 +14,11 @@
#include <kwin_export.h>
namespace KWaylandServer
{
class SurfaceInterface;
}
namespace KWin
{
class AbstractOutput;
......@@ -60,6 +65,11 @@ public:
virtual void doneCurrent() = 0;
virtual QRegion beginFrame(int screenId) = 0;
virtual void endFrame(int screenId, const QRegion &damage, const QRegion &damagedRegion) = 0;
/**
* Tries to directly scan out a surface to the screen)
* @return if the scanout fails (or is not supported on the specified screen)
*/
virtual bool scanout(int screenId, KWaylandServer::SurfaceInterface *surface);
/**
* @brief Returns the OverlayWindow used by the backend.
......@@ -115,6 +125,7 @@ public:
{
return m_haveNativeFence;
}
virtual bool directScanoutAllowed(int screen) const;
/**
* Returns the damage that has accumulated since a buffer of the given age was presented.
......
......@@ -15,8 +15,9 @@
namespace KWin
{
class DrmBuffer
class DrmBuffer : public QObject
{
Q_OBJECT
public:
DrmBuffer(int fd);
virtual ~DrmBuffer() = default;
......
......@@ -20,6 +20,8 @@
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <gbm.h>
// KWaylandServer
#include "KWaylandServer/buffer_interface.h"
namespace KWin
{
......@@ -41,10 +43,15 @@ DrmSurfaceBuffer::DrmSurfaceBuffer(int fd, const std::shared_ptr<GbmSurface> &su
gbm_bo_set_user_data(m_bo, this, nullptr);
}
DrmSurfaceBuffer::DrmSurfaceBuffer(int fd, gbm_bo *buffer)
DrmSurfaceBuffer::DrmSurfaceBuffer(int fd, gbm_bo *buffer, KWaylandServer::BufferInterface *bufferInterface)
: DrmBuffer(fd)
, m_bo(buffer)
, m_bufferInterface(bufferInterface)
{
if (m_bufferInterface) {
m_bufferInterface->ref();
connect(m_bufferInterface, &KWaylandServer::BufferInterface::aboutToBeDestroyed, this, &DrmSurfaceBuffer::clearBufferInterface);
}
m_size = QSize(gbm_bo_get_width(m_bo), gbm_bo_get_height(m_bo));
if (drmModeAddFB(fd, m_size.width(), m_size.height(), 24, 32, gbm_bo_get_stride(m_bo), gbm_bo_get_handle(m_bo).u32, &m_bufferId) != 0) {
qCWarning(KWIN_DRM) << "drmModeAddFB failed";
......@@ -58,16 +65,24 @@ DrmSurfaceBuffer::~DrmSurfaceBuffer()
drmModeRmFB(fd(), m_bufferId);
}
releaseGbm();
if (m_bufferInterface) {
clearBufferInterface();
}
}
void DrmSurfaceBuffer::releaseGbm()
{
if (m_surface) {
m_surface->releaseBuffer(m_bo);
} else if (m_bo) {
gbm_bo_destroy(m_bo);
}
m_bo = nullptr;
}
void DrmSurfaceBuffer::clearBufferInterface()
{
disconnect(m_bufferInterface, &KWaylandServer::BufferInterface::aboutToBeDestroyed, this, &DrmSurfaceBuffer::clearBufferInterface);
m_bufferInterface->unref();
m_bufferInterface = nullptr;
}
}
......@@ -16,6 +16,11 @@
struct gbm_bo;
namespace KWaylandServer
{
class BufferInterface;
}
namespace KWin
{
......@@ -25,7 +30,7 @@ class DrmSurfaceBuffer : public DrmBuffer
{
public:
DrmSurfaceBuffer(int fd, const std::shared_ptr<GbmSurface> &surface);
DrmSurfaceBuffer(int fd, gbm_bo *buffer);
DrmSurfaceBuffer(int fd, gbm_bo *buffer, KWaylandServer::BufferInterface *bufferInterface);
~DrmSurfaceBuffer() override;
bool needsModeChange(DrmBuffer *b) const override {
......@@ -49,6 +54,9 @@ public:
private:
std::shared_ptr<GbmSurface> m_surface;
gbm_bo *m_bo = nullptr;
KWaylandServer::BufferInterface *m_bufferInterface = nullptr;
void clearBufferInterface();
};
}
......
......@@ -17,6 +17,7 @@
#include "renderloop_p.h"
#include "screens.h"
#include "drm_gpu.h"
#include "linux_dmabuf.h"
// kwin libs
#include <kwinglplatform.h>
#include <kwineglimagetexture.h>
......@@ -24,6 +25,12 @@
#include <gbm.h>
#include <unistd.h>
#include <errno.h>
// kwayland server
#include "KWaylandServer/surface_interface.h"
#include "KWaylandServer/buffer_interface.h"
#include "KWaylandServer/linuxdmabuf_v1_interface.h"
#include <egl_dmabuf.h>
#include <drm_fourcc.h>
namespace KWin
{
......@@ -64,8 +71,8 @@ void EglGbmBackend::cleanupOutput(Output &output)
if (output.secondaryGbmBo) {
output.gbmSurface.get()->releaseBuffer(output.secondaryGbmBo);
}
if (output.importedGbmBo) {
gbm_bo_destroy(output.importedGbmBo);
if (output.directScanoutBuffer) {
gbm_bo_destroy(output.directScanoutBuffer);
}
if (output.dmabufFd) {
close(output.dmabufFd);
......@@ -446,8 +453,10 @@ void EglGbmBackend::renderFramebufferToSurface(Output &output)
if (!importedBuffer) {
qCDebug(KWIN_DRM) << "failed to import dma-buf!" << strerror(errno);
} else {
// this buffer automatically gets destroyed by the DrmSurfaceBuffer class
output.importedGbmBo = importedBuffer;
if (output.directScanoutBuffer) {
gbm_bo_destroy(output.directScanoutBuffer);
}
output.directScanoutBuffer = importedBuffer;
}
}
}
......@@ -570,7 +579,9 @@ void EglGbmBackend::aboutToStartPainting(int screenId, const QRegion &damagedReg
bool EglGbmBackend::presentOnOutput(Output &output, const QRegion &damagedRegion)
{
if (isPrimary()) {
if (output.directScanoutBuffer) {
output.buffer = new DrmSurfaceBuffer(m_gpu->fd(), output.directScanoutBuffer, output.bufferInterface);
} else if (isPrimary()) {
if (supportsSwapBuffersWithDamage()) {
QVector<EGLint> rects = regionToRects(output.damageHistory.constFirst(), output.output);
if (!eglSwapBuffersWithDamageEXT(eglDisplay(), output.eglSurface,
......@@ -585,11 +596,9 @@ bool EglGbmBackend::presentOnOutput(Output &output, const QRegion &damagedRegion
}
}
output.buffer = new DrmSurfaceBuffer(m_gpu->fd(), output.gbmSurface);
} else if (output.importedGbmBo == nullptr) {
} else {
qCDebug(KWIN_DRM) << "imported gbm_bo does not exist!";
return false;
} else {
output.buffer = new DrmSurfaceBuffer(m_gpu->fd(), output.importedGbmBo);
}
Q_EMIT output.output->outputChange(damagedRegion);
......@@ -621,18 +630,25 @@ void EglGbmBackend::setViewport(const Output &output) const
QRegion EglGbmBackend::beginFrame(int screenId)
{
if (isPrimary()) {
return prepareRenderingForOutput(m_outputs.at(screenId));
return prepareRenderingForOutput(m_outputs[screenId]);
} else {
return renderingBackend()->beginFrameForSecondaryGpu(m_outputs.at(screenId).output);
}
}
QRegion EglGbmBackend::prepareRenderingForOutput(const Output &output) const
QRegion EglGbmBackend::prepareRenderingForOutput(Output &output) const
{
makeContextCurrent(output);
prepareRenderFramebuffer(output);
setViewport(output);
if (output.directScanoutBuffer) {
gbm_bo_destroy(output.directScanoutBuffer);
output.directScanoutBuffer = nullptr;
output.surfaceInterface = nullptr;
output.bufferInterface = nullptr;
}
if (supportsBufferAge()) {
QRegion region;
......@@ -675,6 +691,71 @@ void EglGbmBackend::endFrame(int screenId, const QRegion &renderedRegion,
}
}
bool EglGbmBackend::scanout(int screenId, KWaylandServer::SurfaceInterface *surface)
{
if (!surface || !surface->buffer() || !surface->buffer()->linuxDmabufBuffer()) {
return false;
}
auto buffer = surface->buffer();
Output output = m_outputs[screenId];
if (buffer->linuxDmabufBuffer()->size() != output.output->modeSize()) {
return false;
}
EglDmabufBuffer *dmabuf = static_cast<EglDmabufBuffer*>(buffer->linuxDmabufBuffer());
if (!dmabuf || !dmabuf->planes().count() ||
!gbm_device_is_format_supported(m_gpu->gbmDevice(), dmabuf->format(), GBM_BO_USE_SCANOUT)) {
return false;
}
gbm_bo *importedBuffer;
if (dmabuf->planes()[0].modifier != DRM_FORMAT_MOD_INVALID
|| dmabuf->planes()[0].offset > 0
|| dmabuf->planes().size() > 1) {
gbm_import_fd_modifier_data data = {};
data.format = dmabuf->format();
data.width = (uint32_t) dmabuf->size().width();
data.height = (uint32_t) dmabuf->size().height();
data.num_fds = dmabuf->planes().count();
data.modifier = dmabuf->planes()[0].modifier;
for (int i = 0; i < dmabuf->planes().count(); i++) {
auto plane = dmabuf->planes()[i];
data.fds[i] = plane.fd;
data.offsets[i] = plane.offset;
data.strides[i] = plane.stride;
}
importedBuffer = gbm_bo_import(m_gpu->gbmDevice(), GBM_BO_IMPORT_FD_MODIFIER, &data, GBM_BO_USE_SCANOUT);
} else {
auto plane = dmabuf->planes()[0];
gbm_import_fd_data data = {};
data.fd = plane.fd;
data.width = (uint32_t) dmabuf->size().width();
data.height = (uint32_t) dmabuf->size().height();
data.stride = plane.stride;
data.format = dmabuf->format();
importedBuffer = gbm_bo_import(m_gpu->gbmDevice(), GBM_BO_IMPORT_FD, &data, GBM_BO_USE_SCANOUT);
}
if (!importedBuffer) {
qCDebug(KWIN_DRM) << "importing the dmabuf for direct scanout failed:" << strerror(errno);
return false;
}
// damage tracking for screen casting
QRegion damage;
if (output.surfaceInterface == surface) {
QRegion trackedDamage = surface->trackedDamage();
surface->resetTrackedDamage();
for (const auto &rect : trackedDamage) {
auto damageRect = QRect(rect);
damageRect.translate(output.output->geometry().topLeft());
damage |= damageRect;
}
} else {
damage = output.output->geometry();
}
output.directScanoutBuffer = importedBuffer;
output.surfaceInterface = surface;
output.bufferInterface = buffer;
return presentOnOutput(output, damage);
}
QSharedPointer<GLTexture> EglGbmBackend::textureForOutput(AbstractOutput *abstractOutput) const
{
const QVector<KWin::EglGbmBackend::Output>::const_iterator itOutput = std::find_if(m_outputs.begin(), m_outputs.end(),
......@@ -693,7 +774,8 @@ QSharedPointer<GLTexture> EglGbmBackend::textureForOutput(AbstractOutput *abstra
return glTexture;
}
EGLImageKHR image = eglCreateImageKHR(eglDisplay(), nullptr, EGL_NATIVE_PIXMAP_KHR, itOutput->buffer->getBo(), nullptr);
EGLImageKHR image = eglCreateImageKHR(eglDisplay(), nullptr, EGL_NATIVE_PIXMAP_KHR,
itOutput->directScanoutBuffer ? itOutput->directScanoutBuffer : itOutput->buffer->getBo(), nullptr);
if (image == EGL_NO_IMAGE_KHR) {
qCWarning(KWIN_DRM) << "Failed to record frame: Error creating EGLImageKHR - " << glGetError();
return {};
......@@ -702,6 +784,11 @@ QSharedPointer<GLTexture> EglGbmBackend::textureForOutput(AbstractOutput *abstra
return QSharedPointer<EGLImageTexture>::create(eglDisplay(), image, GL_RGBA8, drmOutput->modeSize());
}
bool EglGbmBackend::directScanoutAllowed(int screen) const
{
return !m_outputs[screen].output->directScanoutInhibited();
}
/************************************************
* EglTexture
************************************************/
......
......@@ -15,6 +15,11 @@
struct gbm_surface;
struct gbm_bo;
namespace KWaylandServer
{
class BufferInterface;
}
namespace KWin
{
class AbstractOutput;
......@@ -36,6 +41,7 @@ public:
QRegion beginFrame(int screenId) override;
void endFrame(int screenId, const QRegion &damage, const QRegion &damagedRegion) override;
void init() override;
bool scanout(int screenId, KWaylandServer::SurfaceInterface *surface) override;
QSharedPointer<GLTexture> textureForOutput(AbstractOutput *requestedOutput) const override;
......@@ -48,6 +54,8 @@ public:
int getDmabufForSecondaryGpuOutput(AbstractOutput *output, uint32_t *format, uint32_t *stride) override;
QRegion beginFrameForSecondaryGpu(AbstractOutput *output) override;
bool directScanoutAllowed(int screen) const override;
protected:
void cleanupSurfaces() override;
void aboutToStartPainting(int screenId, const QRegion &damage) override;
......@@ -76,7 +84,9 @@ private:
int dmabufFd = 0;
gbm_bo *secondaryGbmBo = nullptr;
gbm_bo *importedGbmBo = nullptr;
gbm_bo *directScanoutBuffer = nullptr;
KWaylandServer::SurfaceInterface *surfaceInterface = nullptr;
KWaylandServer::BufferInterface *bufferInterface = nullptr;
};
bool resetOutput(Output &output, DrmOutput *drmOutput);
......@@ -90,7 +100,7 @@ private:
void prepareRenderFramebuffer(const Output &output) const;
void renderFramebufferToSurface(Output &output);
QRegion prepareRenderingForOutput(const Output &output) const;
QRegion prepareRenderingForOutput(Output &output) const;
bool presentOnOutput(Output &output, const QRegion &damagedRegion);
......
......@@ -72,6 +72,14 @@ void EglMultiBackend::endFrame(int screenId, const QRegion &damage, const QRegio
backend->endFrame(internalScreenId, damage, damagedRegion);
}
bool EglMultiBackend::scanout(int screenId, KWaylandServer::SurfaceInterface *surface)
{
int internalScreenId;
AbstractEglBackend *backend = findBackend(screenId, internalScreenId);
Q_ASSERT(backend != nullptr);
return backend->scanout(internalScreenId, surface);
}
bool EglMultiBackend::makeCurrent()
{
return m_backends[0]->makeCurrent();
......@@ -104,7 +112,7 @@ void EglMultiBackend::screenGeometryChanged(const QSize &size)
Q_UNUSED(size)
}
AbstractEglDrmBackend *EglMultiBackend::findBackend(int screenId, int& internalScreenId)
AbstractEglDrmBackend *EglMultiBackend::findBackend(int screenId, int& internalScreenId) const
{
int screens = 0;
for (int i = 0; i < m_backends.count(); i++) {
......@@ -123,4 +131,12 @@ void EglMultiBackend::addBackend(AbstractEglDrmBackend *backend)
m_backends.append(backend);
}
bool EglMultiBackend::directScanoutAllowed(int screenId) const
{
int internalScreenId;
AbstractEglBackend *backend = findBackend(screenId, internalScreenId);
Q_ASSERT(backend != nullptr);
return backend->directScanoutAllowed(internalScreenId);
}
}