b3a19f9e5b
Summary: Selecting not to vsync does not make sense for an X11 compositor. In the end we want clients to be able to present async if they want to but the compositor is supposed to send swaps with vsync to the XServer in order to not generate tearing artifacts. There was also a detection logic which did some questionable things in case vsync was not available. I don't think this is necessary at all since we can just always run a timer to present with or without vsync. Test Plan: kwin_x11 tested on i915. Reviewers: #kwin, zzag Subscribers: zzag, kwin Tags: #kwin Maniphest Tasks: T11071 Differential Revision: https://phabricator.kde.org/D23511
691 lines
23 KiB
C++
691 lines
23 KiB
C++
/********************************************************************
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
Copyright (C) 2019 NVIDIA Inc.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*********************************************************************/
|
|
#include "egl_stream_backend.h"
|
|
#include "composite.h"
|
|
#include "drm_backend.h"
|
|
#include "drm_output.h"
|
|
#include "drm_object_crtc.h"
|
|
#include "drm_object_plane.h"
|
|
#include "logging.h"
|
|
#include "logind.h"
|
|
#include "options.h"
|
|
#include "scene.h"
|
|
#include "screens.h"
|
|
#include "wayland_server.h"
|
|
#include <kwinglplatform.h>
|
|
#include <QOpenGLContext>
|
|
#include <KWayland/Server/surface_interface.h>
|
|
#include <KWayland/Server/buffer_interface.h>
|
|
#include <KWayland/Server/eglstream_controller_interface.h>
|
|
#include <KWayland/Server/display.h>
|
|
#include <KWayland/Server/resource.h>
|
|
#include <wayland-server-core.h>
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
typedef EGLStreamKHR (*PFNEGLCREATESTREAMATTRIBNV)(EGLDisplay, EGLAttrib *);
|
|
typedef EGLBoolean (*PFNEGLGETOUTPUTLAYERSEXT)(EGLDisplay, EGLAttrib *, EGLOutputLayerEXT *, EGLint, EGLint *);
|
|
typedef EGLBoolean (*PFNEGLSTREAMCONSUMEROUTPUTEXT)(EGLDisplay, EGLStreamKHR, EGLOutputLayerEXT);
|
|
typedef EGLSurface (*PFNEGLCREATESTREAMPRODUCERSURFACEKHR)(EGLDisplay, EGLConfig, EGLStreamKHR, EGLint *);
|
|
typedef EGLBoolean (*PFNEGLDESTROYSTREAMKHR)(EGLDisplay, EGLStreamKHR);
|
|
typedef EGLBoolean (*PFNEGLSTREAMCONSUMERACQUIREATTRIBNV)(EGLDisplay, EGLStreamKHR, EGLAttrib *);
|
|
typedef EGLBoolean (*PFNEGLSTREAMCONSUMERGLTEXTUREEXTERNALKHR)(EGLDisplay, EGLStreamKHR);
|
|
typedef EGLBoolean (*PFNEGLQUERYSTREAMATTRIBNV)(EGLDisplay, EGLStreamKHR, EGLenum, EGLAttrib *);
|
|
typedef EGLBoolean (*PFNEGLSTREAMCONSUMERRELEASEKHR)(EGLDisplay, EGLStreamKHR);
|
|
typedef EGLBoolean (*PFNEGLQUERYWAYLANDBUFFERWL)(EGLDisplay, wl_resource *, EGLint, EGLint *);
|
|
PFNEGLCREATESTREAMATTRIBNV pEglCreateStreamAttribNV = nullptr;
|
|
PFNEGLGETOUTPUTLAYERSEXT pEglGetOutputLayersEXT = nullptr;
|
|
PFNEGLSTREAMCONSUMEROUTPUTEXT pEglStreamConsumerOutputEXT = nullptr;
|
|
PFNEGLCREATESTREAMPRODUCERSURFACEKHR pEglCreateStreamProducerSurfaceKHR = nullptr;
|
|
PFNEGLDESTROYSTREAMKHR pEglDestroyStreamKHR = nullptr;
|
|
PFNEGLSTREAMCONSUMERACQUIREATTRIBNV pEglStreamConsumerAcquireAttribNV = nullptr;
|
|
PFNEGLSTREAMCONSUMERGLTEXTUREEXTERNALKHR pEglStreamConsumerGLTextureExternalKHR = nullptr;
|
|
PFNEGLQUERYSTREAMATTRIBNV pEglQueryStreamAttribNV = nullptr;
|
|
PFNEGLSTREAMCONSUMERRELEASEKHR pEglStreamConsumerReleaseKHR = nullptr;
|
|
PFNEGLQUERYWAYLANDBUFFERWL pEglQueryWaylandBufferWL = nullptr;
|
|
|
|
#ifndef EGL_CONSUMER_AUTO_ACQUIRE_EXT
|
|
#define EGL_CONSUMER_AUTO_ACQUIRE_EXT 0x332B
|
|
#endif
|
|
|
|
#ifndef EGL_DRM_MASTER_FD_EXT
|
|
#define EGL_DRM_MASTER_FD_EXT 0x333C
|
|
#endif
|
|
|
|
#ifndef EGL_DRM_FLIP_EVENT_DATA_NV
|
|
#define EGL_DRM_FLIP_EVENT_DATA_NV 0x333E
|
|
#endif
|
|
|
|
#ifndef EGL_WAYLAND_EGLSTREAM_WL
|
|
#define EGL_WAYLAND_EGLSTREAM_WL 0x334B
|
|
#endif
|
|
|
|
#ifndef EGL_WAYLAND_Y_INVERTED_WL
|
|
#define EGL_WAYLAND_Y_INVERTED_WL 0x31DB
|
|
#endif
|
|
|
|
EglStreamBackend::EglStreamBackend(DrmBackend *b)
|
|
: AbstractEglBackend(), m_backend(b)
|
|
{
|
|
setIsDirectRendering(true);
|
|
connect(m_backend, &DrmBackend::outputAdded, this, &EglStreamBackend::createOutput);
|
|
connect(m_backend, &DrmBackend::outputRemoved, this,
|
|
[this] (DrmOutput *output) {
|
|
auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
|
|
[output] (const Output &o) {
|
|
return o.output == output;
|
|
});
|
|
if (it == m_outputs.end()) {
|
|
return;
|
|
}
|
|
cleanupOutput(*it);
|
|
m_outputs.erase(it);
|
|
});
|
|
}
|
|
|
|
EglStreamBackend::~EglStreamBackend()
|
|
{
|
|
cleanup();
|
|
}
|
|
|
|
void EglStreamBackend::cleanupSurfaces()
|
|
{
|
|
for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) {
|
|
cleanupOutput(*it);
|
|
}
|
|
m_outputs.clear();
|
|
}
|
|
|
|
void EglStreamBackend::cleanupOutput(const Output &o)
|
|
{
|
|
if (o.buffer != nullptr) {
|
|
delete o.buffer;
|
|
}
|
|
if (o.eglSurface != EGL_NO_SURFACE) {
|
|
eglDestroySurface(eglDisplay(), o.eglSurface);
|
|
}
|
|
if (o.eglStream != EGL_NO_STREAM_KHR) {
|
|
pEglDestroyStreamKHR(eglDisplay(), o.eglStream);
|
|
}
|
|
}
|
|
|
|
bool EglStreamBackend::initializeEgl()
|
|
{
|
|
initClientExtensions();
|
|
EGLDisplay display = m_backend->sceneEglDisplay();
|
|
if (display == EGL_NO_DISPLAY) {
|
|
if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_device_base")) &&
|
|
!(hasClientExtension(QByteArrayLiteral("EGL_EXT_device_query")) &&
|
|
hasClientExtension(QByteArrayLiteral("EGL_EXT_device_enumeration")))) {
|
|
setFailed("Missing required EGL client extension: "
|
|
"EGL_EXT_device_base or "
|
|
"EGL_EXT_device_query and EGL_EXT_device_enumeration");
|
|
return false;
|
|
}
|
|
|
|
// Try to find the EGLDevice corresponding to our DRM device file
|
|
int numDevices;
|
|
eglQueryDevicesEXT(0, nullptr, &numDevices);
|
|
QVector<EGLDeviceEXT> devices(numDevices);
|
|
eglQueryDevicesEXT(numDevices, devices.data(), &numDevices);
|
|
for (EGLDeviceEXT device : devices) {
|
|
const char *drmDeviceFile = eglQueryDeviceStringEXT(device, EGL_DRM_DEVICE_FILE_EXT);
|
|
if (m_backend->devNode() != drmDeviceFile) {
|
|
continue;
|
|
}
|
|
|
|
const char *deviceExtensionCString = eglQueryDeviceStringEXT(device, EGL_EXTENSIONS);
|
|
QByteArray deviceExtensions = QByteArray::fromRawData(deviceExtensionCString,
|
|
qstrlen(deviceExtensionCString));
|
|
if (!deviceExtensions.split(' ').contains(QByteArrayLiteral("EGL_EXT_device_drm"))) {
|
|
continue;
|
|
}
|
|
|
|
EGLint platformAttribs[] = {
|
|
EGL_DRM_MASTER_FD_EXT, m_backend->fd(),
|
|
EGL_NONE
|
|
};
|
|
display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, device, platformAttribs);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (display == EGL_NO_DISPLAY) {
|
|
setFailed("No suitable EGL device found");
|
|
return false;
|
|
}
|
|
|
|
setEglDisplay(display);
|
|
if (!initEglAPI()) {
|
|
return false;
|
|
}
|
|
|
|
const QVector<QByteArray> requiredExtensions = {
|
|
QByteArrayLiteral("EGL_EXT_output_base"),
|
|
QByteArrayLiteral("EGL_EXT_output_drm"),
|
|
QByteArrayLiteral("EGL_KHR_stream"),
|
|
QByteArrayLiteral("EGL_KHR_stream_producer_eglsurface"),
|
|
QByteArrayLiteral("EGL_EXT_stream_consumer_egloutput"),
|
|
QByteArrayLiteral("EGL_NV_stream_attrib"),
|
|
QByteArrayLiteral("EGL_EXT_stream_acquire_mode"),
|
|
QByteArrayLiteral("EGL_KHR_stream_consumer_gltexture"),
|
|
QByteArrayLiteral("EGL_WL_wayland_eglstream")
|
|
};
|
|
for (const QByteArray &ext : requiredExtensions) {
|
|
if (!hasExtension(ext)) {
|
|
setFailed(QStringLiteral("Missing required EGL extension: ") + ext);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
pEglCreateStreamAttribNV = (PFNEGLCREATESTREAMATTRIBNV)eglGetProcAddress("eglCreateStreamAttribNV");
|
|
pEglGetOutputLayersEXT = (PFNEGLGETOUTPUTLAYERSEXT)eglGetProcAddress("eglGetOutputLayersEXT");
|
|
pEglStreamConsumerOutputEXT = (PFNEGLSTREAMCONSUMEROUTPUTEXT)eglGetProcAddress("eglStreamConsumerOutputEXT");
|
|
pEglCreateStreamProducerSurfaceKHR = (PFNEGLCREATESTREAMPRODUCERSURFACEKHR)eglGetProcAddress("eglCreateStreamProducerSurfaceKHR");
|
|
pEglDestroyStreamKHR = (PFNEGLDESTROYSTREAMKHR)eglGetProcAddress("eglDestroyStreamKHR");
|
|
pEglStreamConsumerAcquireAttribNV = (PFNEGLSTREAMCONSUMERACQUIREATTRIBNV)eglGetProcAddress("eglStreamConsumerAcquireAttribNV");
|
|
pEglStreamConsumerGLTextureExternalKHR = (PFNEGLSTREAMCONSUMERGLTEXTUREEXTERNALKHR)eglGetProcAddress("eglStreamConsumerGLTextureExternalKHR");
|
|
pEglQueryStreamAttribNV = (PFNEGLQUERYSTREAMATTRIBNV)eglGetProcAddress("eglQueryStreamAttribNV");
|
|
pEglStreamConsumerReleaseKHR = (PFNEGLSTREAMCONSUMERRELEASEKHR)eglGetProcAddress("eglStreamConsumerReleaseKHR");
|
|
pEglQueryWaylandBufferWL = (PFNEGLQUERYWAYLANDBUFFERWL)eglGetProcAddress("eglQueryWaylandBufferWL");
|
|
return true;
|
|
}
|
|
|
|
EglStreamBackend::StreamTexture *EglStreamBackend::lookupStreamTexture(KWayland::Server::SurfaceInterface *surface)
|
|
{
|
|
auto it = m_streamTextures.find(surface);
|
|
return it != m_streamTextures.end() ?
|
|
&it.value() :
|
|
nullptr;
|
|
}
|
|
|
|
void EglStreamBackend::attachStreamConsumer(KWayland::Server::SurfaceInterface *surface,
|
|
void *eglStream,
|
|
wl_array *attribs)
|
|
{
|
|
QVector<EGLAttrib> streamAttribs;
|
|
streamAttribs << EGL_WAYLAND_EGLSTREAM_WL << (EGLAttrib)eglStream;
|
|
EGLAttrib *attribArray = (EGLAttrib *)attribs->data;
|
|
for (unsigned int i = 0; i < attribs->size; ++i) {
|
|
streamAttribs << attribArray[i];
|
|
}
|
|
streamAttribs << EGL_NONE;
|
|
|
|
EGLStreamKHR stream = pEglCreateStreamAttribNV(eglDisplay(), streamAttribs.data());
|
|
if (stream == EGL_NO_STREAM_KHR) {
|
|
qCWarning(KWIN_DRM) << "Failed to create EGL stream";
|
|
return;
|
|
}
|
|
|
|
GLuint texture;
|
|
StreamTexture *st = lookupStreamTexture(surface);
|
|
if (st != nullptr) {
|
|
pEglDestroyStreamKHR(eglDisplay(), st->stream);
|
|
st->stream = stream;
|
|
texture = st->texture;
|
|
} else {
|
|
StreamTexture newSt = { stream, 0 };
|
|
glGenTextures(1, &newSt.texture);
|
|
m_streamTextures.insert(surface, newSt);
|
|
texture = newSt.texture;
|
|
|
|
connect(surface, &KWayland::Server::Resource::unbound, this,
|
|
[surface, this]() {
|
|
const StreamTexture &st = m_streamTextures.take(surface);
|
|
pEglDestroyStreamKHR(eglDisplay(), st.stream);
|
|
glDeleteTextures(1, &st.texture);
|
|
});
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
|
|
if (!pEglStreamConsumerGLTextureExternalKHR(eglDisplay(), stream)) {
|
|
qCWarning(KWIN_DRM) << "Failed to bind EGL stream to texture";
|
|
}
|
|
glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
|
|
}
|
|
|
|
void EglStreamBackend::init()
|
|
{
|
|
if (!m_backend->atomicModeSetting()) {
|
|
setFailed("EGLStream backend requires atomic modesetting");
|
|
return;
|
|
}
|
|
|
|
if (!initializeEgl()) {
|
|
setFailed("Failed to initialize EGL api");
|
|
return;
|
|
}
|
|
if (!initRenderingContext()) {
|
|
setFailed("Failed to initialize rendering context");
|
|
return;
|
|
}
|
|
|
|
initKWinGL();
|
|
setSupportsBufferAge(false);
|
|
initWayland();
|
|
|
|
using namespace KWayland::Server;
|
|
m_eglStreamControllerInterface = waylandServer()->display()->createEglStreamControllerInterface();
|
|
connect(m_eglStreamControllerInterface, &EglStreamControllerInterface::streamConsumerAttached, this,
|
|
&EglStreamBackend::attachStreamConsumer);
|
|
m_eglStreamControllerInterface->create();
|
|
if (!m_eglStreamControllerInterface->isValid()) {
|
|
setFailed("failed to initialize wayland-eglstream-controller interface");
|
|
}
|
|
}
|
|
|
|
bool EglStreamBackend::initRenderingContext()
|
|
{
|
|
initBufferConfigs();
|
|
|
|
if (!createContext()) {
|
|
return false;
|
|
}
|
|
|
|
const auto outputs = m_backend->drmOutputs();
|
|
for (DrmOutput *drmOutput : outputs) {
|
|
createOutput(drmOutput);
|
|
}
|
|
if (m_outputs.isEmpty()) {
|
|
qCCritical(KWIN_DRM) << "Failed to create output surface";
|
|
return false;
|
|
}
|
|
// set our first surface as the one for the abstract backend
|
|
setSurface(m_outputs.first().eglSurface);
|
|
|
|
return makeContextCurrent(m_outputs.first());
|
|
}
|
|
|
|
bool EglStreamBackend::resetOutput(Output &o, DrmOutput *drmOutput)
|
|
{
|
|
o.output = drmOutput;
|
|
if (o.buffer != nullptr) {
|
|
delete o.buffer;
|
|
}
|
|
// dumb buffer used for modesetting
|
|
o.buffer = m_backend->createBuffer(drmOutput->pixelSize());
|
|
|
|
EGLAttrib streamAttribs[] = {
|
|
EGL_STREAM_FIFO_LENGTH_KHR, 0, // mailbox mode
|
|
EGL_CONSUMER_AUTO_ACQUIRE_EXT, EGL_FALSE,
|
|
EGL_NONE
|
|
};
|
|
EGLStreamKHR stream = pEglCreateStreamAttribNV(eglDisplay(), streamAttribs);
|
|
if (stream == EGL_NO_STREAM_KHR) {
|
|
qCCritical(KWIN_DRM) << "Failed to create EGL stream for output";
|
|
return false;
|
|
}
|
|
|
|
EGLAttrib outputAttribs[3];
|
|
if (drmOutput->primaryPlane()) {
|
|
outputAttribs[0] = EGL_DRM_PLANE_EXT;
|
|
outputAttribs[1] = drmOutput->primaryPlane()->id();
|
|
} else {
|
|
outputAttribs[0] = EGL_DRM_CRTC_EXT;
|
|
outputAttribs[1] = drmOutput->crtc()->id();
|
|
}
|
|
outputAttribs[2] = EGL_NONE;
|
|
EGLint numLayers;
|
|
EGLOutputLayerEXT outputLayer;
|
|
pEglGetOutputLayersEXT(eglDisplay(), outputAttribs, &outputLayer, 1, &numLayers);
|
|
if (numLayers == 0) {
|
|
qCCritical(KWIN_DRM) << "No EGL output layers found";
|
|
return false;
|
|
}
|
|
|
|
pEglStreamConsumerOutputEXT(eglDisplay(), stream, outputLayer);
|
|
EGLint streamProducerAttribs[] = {
|
|
EGL_WIDTH, drmOutput->pixelSize().width(),
|
|
EGL_HEIGHT, drmOutput->pixelSize().height(),
|
|
EGL_NONE
|
|
};
|
|
EGLSurface eglSurface = pEglCreateStreamProducerSurfaceKHR(eglDisplay(), config(), stream,
|
|
streamProducerAttribs);
|
|
if (eglSurface == EGL_NO_SURFACE) {
|
|
qCCritical(KWIN_DRM) << "Failed to create EGL surface for output";
|
|
return false;
|
|
}
|
|
|
|
if (o.eglSurface != EGL_NO_SURFACE) {
|
|
if (surface() == o.eglSurface) {
|
|
setSurface(eglSurface);
|
|
}
|
|
eglDestroySurface(eglDisplay(), o.eglSurface);
|
|
}
|
|
|
|
if (o.eglStream != EGL_NO_STREAM_KHR) {
|
|
pEglDestroyStreamKHR(eglDisplay(), o.eglStream);
|
|
}
|
|
|
|
o.eglStream = stream;
|
|
o.eglSurface = eglSurface;
|
|
return true;
|
|
}
|
|
|
|
void EglStreamBackend::createOutput(DrmOutput *drmOutput)
|
|
{
|
|
Output o;
|
|
if (!resetOutput(o, drmOutput)) {
|
|
return;
|
|
}
|
|
|
|
connect(drmOutput, &DrmOutput::modeChanged, this,
|
|
[drmOutput, this] {
|
|
auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
|
|
[drmOutput] (const auto &o) {
|
|
return o.output == drmOutput;
|
|
}
|
|
);
|
|
if (it == m_outputs.end()) {
|
|
return;
|
|
}
|
|
resetOutput(*it, drmOutput);
|
|
}
|
|
);
|
|
m_outputs << o;
|
|
}
|
|
|
|
bool EglStreamBackend::makeContextCurrent(const Output &output)
|
|
{
|
|
const EGLSurface surface = output.eglSurface;
|
|
if (surface == EGL_NO_SURFACE) {
|
|
return false;
|
|
}
|
|
|
|
if (eglMakeCurrent(eglDisplay(), surface, surface, context()) == EGL_FALSE) {
|
|
qCCritical(KWIN_DRM) << "Failed to make EGL context current";
|
|
return false;
|
|
}
|
|
|
|
EGLint error = eglGetError();
|
|
if (error != EGL_SUCCESS) {
|
|
qCWarning(KWIN_DRM) << "Error occurred while making EGL context current" << error;
|
|
return false;
|
|
}
|
|
|
|
const QSize &overall = screens()->size();
|
|
const QRect &v = output.output->geometry();
|
|
qreal scale = output.output->scale();
|
|
glViewport(-v.x() * scale, (v.height() - overall.height() + v.y()) * scale,
|
|
overall.width() * scale, overall.height() * scale);
|
|
return true;
|
|
}
|
|
|
|
bool EglStreamBackend::initBufferConfigs()
|
|
{
|
|
const EGLint configAttribs[] = {
|
|
EGL_SURFACE_TYPE, EGL_STREAM_BIT_KHR,
|
|
EGL_RED_SIZE, 1,
|
|
EGL_GREEN_SIZE, 1,
|
|
EGL_BLUE_SIZE, 1,
|
|
EGL_ALPHA_SIZE, 0,
|
|
EGL_RENDERABLE_TYPE, isOpenGLES() ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT,
|
|
EGL_CONFIG_CAVEAT, EGL_NONE,
|
|
EGL_NONE,
|
|
};
|
|
EGLint count;
|
|
EGLConfig config;
|
|
if (!eglChooseConfig(eglDisplay(), configAttribs, &config, 1, &count)) {
|
|
qCCritical(KWIN_DRM) << "Failed to query available EGL configs";
|
|
return false;
|
|
}
|
|
if (count == 0) {
|
|
qCCritical(KWIN_DRM) << "No suitable EGL config found";
|
|
return false;
|
|
}
|
|
|
|
setConfig(config);
|
|
return true;
|
|
}
|
|
|
|
void EglStreamBackend::present()
|
|
{
|
|
for (auto &o : m_outputs) {
|
|
makeContextCurrent(o);
|
|
presentOnOutput(o);
|
|
}
|
|
}
|
|
|
|
void EglStreamBackend::presentOnOutput(EglStreamBackend::Output &o)
|
|
{
|
|
eglSwapBuffers(eglDisplay(), o.eglSurface);
|
|
if (!m_backend->present(o.buffer, o.output)) {
|
|
return;
|
|
}
|
|
|
|
EGLAttrib acquireAttribs[] = {
|
|
EGL_DRM_FLIP_EVENT_DATA_NV, (EGLAttrib)o.output,
|
|
EGL_NONE,
|
|
};
|
|
if (!pEglStreamConsumerAcquireAttribNV(eglDisplay(), o.eglStream, acquireAttribs)) {
|
|
qCWarning(KWIN_DRM) << "Failed to acquire output EGL stream frame";
|
|
}
|
|
}
|
|
|
|
void EglStreamBackend::screenGeometryChanged(const QSize &size)
|
|
{
|
|
Q_UNUSED(size)
|
|
}
|
|
|
|
SceneOpenGLTexturePrivate *EglStreamBackend::createBackendTexture(SceneOpenGLTexture *texture)
|
|
{
|
|
return new EglStreamTexture(texture, this);
|
|
}
|
|
|
|
QRegion EglStreamBackend::prepareRenderingFrame()
|
|
{
|
|
startRenderTimer();
|
|
return QRegion();
|
|
}
|
|
|
|
QRegion EglStreamBackend::prepareRenderingForScreen(int screenId)
|
|
{
|
|
const Output &o = m_outputs.at(screenId);
|
|
makeContextCurrent(o);
|
|
return o.output->geometry();
|
|
}
|
|
|
|
void EglStreamBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
|
|
{
|
|
Q_UNUSED(renderedRegion)
|
|
Q_UNUSED(damagedRegion)
|
|
}
|
|
|
|
void EglStreamBackend::endRenderingFrameForScreen(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion)
|
|
{
|
|
Q_UNUSED(renderedRegion);
|
|
Q_UNUSED(damagedRegion);
|
|
Output &o = m_outputs[screenId];
|
|
presentOnOutput(o);
|
|
}
|
|
|
|
bool EglStreamBackend::usesOverlayWindow() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool EglStreamBackend::perScreenRendering() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/************************************************
|
|
* EglTexture
|
|
************************************************/
|
|
|
|
EglStreamTexture::EglStreamTexture(SceneOpenGLTexture *texture, EglStreamBackend *backend)
|
|
: AbstractEglTexture(texture, backend), m_backend(backend), m_fbo(0), m_rbo(0)
|
|
{
|
|
}
|
|
|
|
EglStreamTexture::~EglStreamTexture()
|
|
{
|
|
glDeleteRenderbuffers(1, &m_rbo);
|
|
glDeleteFramebuffers(1, &m_fbo);
|
|
}
|
|
|
|
bool EglStreamTexture::acquireStreamFrame(EGLStreamKHR stream)
|
|
{
|
|
EGLAttrib streamState;
|
|
if (!pEglQueryStreamAttribNV(m_backend->eglDisplay(), stream,
|
|
EGL_STREAM_STATE_KHR, &streamState)) {
|
|
qCWarning(KWIN_DRM) << "Failed to query EGL stream state";
|
|
return false;
|
|
}
|
|
|
|
if (streamState == EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR) {
|
|
if (pEglStreamConsumerAcquireAttribNV(m_backend->eglDisplay(), stream, nullptr)) {
|
|
return true;
|
|
} else {
|
|
qCWarning(KWIN_DRM) << "Failed to acquire EGL stream frame";
|
|
}
|
|
}
|
|
|
|
// Re-use previous texture contents if no new frame is available
|
|
// or if acquisition fails for some reason
|
|
return false;
|
|
}
|
|
|
|
void EglStreamTexture::createFbo()
|
|
{
|
|
glDeleteRenderbuffers(1, &m_rbo);
|
|
glDeleteFramebuffers(1, &m_fbo);
|
|
|
|
glGenFramebuffers(1, &m_fbo);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
|
|
glGenRenderbuffers(1, &m_rbo);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
|
|
glRenderbufferStorage(GL_RENDERBUFFER, m_format, m_size.width(), m_size.height());
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
}
|
|
|
|
// Renders the contents of the given EXTERNAL_OES texture
|
|
// to the scratch framebuffer, then copies this to m_texture
|
|
void EglStreamTexture::copyExternalTexture(GLuint tex)
|
|
{
|
|
GLint oldViewport[4], oldProgram;
|
|
glGetIntegerv(GL_VIEWPORT, oldViewport);
|
|
glViewport(0, 0, m_size.width(), m_size.height());
|
|
glGetIntegerv(GL_CURRENT_PROGRAM, &oldProgram);
|
|
glUseProgram(0);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
|
|
glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
|
|
glEnable(GL_TEXTURE_EXTERNAL_OES);
|
|
|
|
GLfloat yTop = texture()->isYInverted() ? 0 : 1;
|
|
glBegin(GL_QUADS);
|
|
glTexCoord2f(0, yTop);
|
|
glVertex2f(-1, 1);
|
|
glTexCoord2f(0, 1 - yTop);
|
|
glVertex2f(-1, -1);
|
|
glTexCoord2f(1, 1 - yTop);
|
|
glVertex2f(1, -1);
|
|
glTexCoord2f(1, yTop);
|
|
glVertex2f(1, 1);
|
|
glEnd();
|
|
|
|
texture()->bind();
|
|
glCopyTexImage2D(m_target, 0, m_format, 0, 0, m_size.width(), m_size.height(), 0);
|
|
texture()->unbind();
|
|
|
|
glDisable(GL_TEXTURE_EXTERNAL_OES);
|
|
glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
glUseProgram(oldProgram);
|
|
glViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
|
|
}
|
|
|
|
bool EglStreamTexture::attachBuffer(KWayland::Server::BufferInterface *buffer)
|
|
{
|
|
QSize oldSize = m_size;
|
|
m_size = buffer->size();
|
|
GLenum oldFormat = m_format;
|
|
m_format = buffer->hasAlphaChannel() ? GL_RGBA : GL_RGB;
|
|
|
|
EGLint yInverted, wasYInverted = texture()->isYInverted();
|
|
if (!pEglQueryWaylandBufferWL(m_backend->eglDisplay(), buffer->resource(), EGL_WAYLAND_Y_INVERTED_WL, &yInverted)) {
|
|
yInverted = EGL_TRUE;
|
|
}
|
|
texture()->setYInverted(yInverted);
|
|
updateMatrix();
|
|
|
|
return oldSize != m_size ||
|
|
oldFormat != m_format ||
|
|
wasYInverted != texture()->isYInverted();
|
|
}
|
|
|
|
bool EglStreamTexture::loadTexture(WindowPixmap *pixmap)
|
|
{
|
|
using namespace KWayland::Server;
|
|
SurfaceInterface *surface = pixmap->surface();
|
|
const EglStreamBackend::StreamTexture *st = m_backend->lookupStreamTexture(surface);
|
|
if (!pixmap->buffer().isNull() && st != nullptr) {
|
|
|
|
glGenTextures(1, &m_texture);
|
|
texture()->setWrapMode(GL_CLAMP_TO_EDGE);
|
|
texture()->setFilter(GL_LINEAR);
|
|
|
|
attachBuffer(surface->buffer());
|
|
createFbo();
|
|
surface->resetTrackedDamage();
|
|
|
|
if (acquireStreamFrame(st->stream)) {
|
|
copyExternalTexture(st->texture);
|
|
if (!pEglStreamConsumerReleaseKHR(m_backend->eglDisplay(), st->stream)) {
|
|
qCWarning(KWIN_DRM) << "Failed to release EGL stream";
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
// Not an EGLStream surface
|
|
return AbstractEglTexture::loadTexture(pixmap);
|
|
}
|
|
}
|
|
|
|
void EglStreamTexture::updateTexture(WindowPixmap *pixmap)
|
|
{
|
|
using namespace KWayland::Server;
|
|
SurfaceInterface *surface = pixmap->surface();
|
|
const EglStreamBackend::StreamTexture *st = m_backend->lookupStreamTexture(surface);
|
|
if (!pixmap->buffer().isNull() && st != nullptr) {
|
|
|
|
if (attachBuffer(surface->buffer())) {
|
|
createFbo();
|
|
}
|
|
surface->resetTrackedDamage();
|
|
|
|
if (acquireStreamFrame(st->stream)) {
|
|
copyExternalTexture(st->texture);
|
|
if (!pEglStreamConsumerReleaseKHR(m_backend->eglDisplay(), st->stream)) {
|
|
qCWarning(KWIN_DRM) << "Failed to release EGL stream";
|
|
}
|
|
}
|
|
} else {
|
|
// Not an EGLStream surface
|
|
AbstractEglTexture::updateTexture(pixmap);
|
|
}
|
|
}
|
|
|
|
} // namespace
|