KWin
Loading...
Searching...
No Matches
x11_standalone_egl_backend.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2010, 2012 Martin Gräßlin <mgraesslin@kde.org>
3 SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
9#include "compositor.h"
10#include "core/outputbackend.h"
11#include "core/outputlayer.h"
12#include "core/overlaywindow.h"
13#include "core/renderloop_p.h"
14#include "opengl/glplatform.h"
16#include "options.h"
18#include "utils/c_ptr.h"
20#include "workspace.h"
24
25#include <QOpenGLContext>
26#include <drm_fourcc.h>
27
28namespace KWin
29{
30
32 : m_backend(backend)
33{
34}
35
36std::optional<OutputLayerBeginFrameInfo> EglLayer::beginFrame()
37{
38 return m_backend->beginFrame();
39}
40
41bool EglLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
42{
43 m_backend->endFrame(renderedRegion, damagedRegion);
44 return true;
45}
46
47std::chrono::nanoseconds EglLayer::queryRenderTime() const
48{
49 return m_backend->queryRenderTime();
50}
51
53 : m_backend(backend)
54 , m_overlayWindow(std::make_unique<OverlayWindowX11>(backend))
55 , m_layer(std::make_unique<EglLayer>(this))
56{
57 // There is no any way to determine when a buffer swap completes with EGL. Fallback
58 // to software vblank events. Could we use the Present extension to get notified when
59 // the overlay window is actually presented on the screen?
60 m_vsyncMonitor = SoftwareVsyncMonitor::create();
61 connect(backend->renderLoop(), &RenderLoop::refreshRateChanged, this, [this, backend]() {
62 m_vsyncMonitor->setRefreshRate(backend->renderLoop()->refreshRate());
63 });
64 m_vsyncMonitor->setRefreshRate(backend->renderLoop()->refreshRate());
65
66 connect(m_vsyncMonitor.get(), &VsyncMonitor::vblankOccurred, this, &EglBackend::vblank);
67 Q_ASSERT(workspace());
68 connect(workspace(), &Workspace::geometryChanged, this, &EglBackend::screenGeometryChanged);
69 overlayWindow()->resize(workspace()->geometry().size());
70}
71
73{
74 // No completion events will be received for in-flight frames, this may lock the
75 // render loop. We need to ensure that the render loop is back to its initial state
76 // if the render backend is about to be destroyed.
78
79 m_query.reset();
80
81 if (isFailed() && m_overlayWindow) {
82 m_overlayWindow->destroy();
83 }
84 cleanup();
85
86 if (m_overlayWindow && m_overlayWindow->window()) {
87 m_overlayWindow->destroy();
88 }
89}
90
91std::unique_ptr<SurfaceTexture> EglBackend::createSurfaceTextureX11(SurfacePixmapX11 *texture)
92{
93 return std::make_unique<EglSurfaceTextureX11>(this, texture);
94}
95
97{
98 QOpenGLContext *qtShareContext = QOpenGLContext::globalShareContext();
99 ::EGLDisplay shareDisplay = EGL_NO_DISPLAY;
100 ::EGLContext shareContext = EGL_NO_CONTEXT;
101 if (qtShareContext) {
102 qDebug(KWIN_X11STANDALONE) << "Global share context format:" << qtShareContext->format();
103 const auto nativeHandle = qtShareContext->nativeInterface<QNativeInterface::QEGLContext>();
104 if (nativeHandle) {
105 shareContext = nativeHandle->nativeContext();
106 shareDisplay = nativeHandle->display();
107 } else {
108 setFailed(QStringLiteral("Invalid QOpenGLContext::globalShareContext()"));
109 return;
110 }
111 }
112 if (shareContext == EGL_NO_CONTEXT) {
113 setFailed(QStringLiteral("QOpenGLContext::globalShareContext() is required"));
114 return;
115 }
116
117 m_fbo = std::make_unique<GLFramebuffer>(0, workspace()->geometry().size());
118
119 m_backend->setEglDisplay(EglDisplay::create(shareDisplay, false));
120 kwinApp()->outputBackend()->setSceneEglGlobalShareContext(shareContext);
121
122 qputenv("EGL_PLATFORM", "x11");
123 if (!initRenderingContext()) {
124 setFailed(QStringLiteral("Could not initialize rendering context"));
125 return;
126 }
127
128 initKWinGL();
129
130 if (!hasExtension(QByteArrayLiteral("EGL_KHR_image")) && (!hasExtension(QByteArrayLiteral("EGL_KHR_image_base")) || !hasExtension(QByteArrayLiteral("EGL_KHR_image_pixmap")))) {
131 setFailed(QStringLiteral("Required support for binding pixmaps to EGLImages not found, disabling compositing"));
132 return;
133 }
134 if (!hasGLExtension(QByteArrayLiteral("GL_OES_EGL_image"))) {
135 setFailed(QStringLiteral("Required extension GL_OES_EGL_image not found, disabling compositing"));
136 return;
137 }
138
139 // check for EGL_NV_post_sub_buffer and whether it can be used on the surface
140 if (hasExtension(QByteArrayLiteral("EGL_NV_post_sub_buffer"))) {
141 if (eglQuerySurface(eglDisplayObject()->handle(), surface(), EGL_POST_SUB_BUFFER_SUPPORTED_NV, &m_havePostSubBuffer) == EGL_FALSE) {
142 EGLint error = eglGetError();
143 if (error != EGL_SUCCESS && error != EGL_BAD_ATTRIBUTE) {
144 setFailed(QStringLiteral("query surface failed"));
145 return;
146 } else {
147 m_havePostSubBuffer = EGL_FALSE;
148 }
149 }
150 }
151
152 if (m_havePostSubBuffer) {
153 qCDebug(KWIN_CORE) << "EGL implementation and surface support eglPostSubBufferNV, let's use it";
154
155 // check if swap interval 1 is supported
156 EGLint val;
157 eglGetConfigAttrib(eglDisplayObject()->handle(), config(), EGL_MAX_SWAP_INTERVAL, &val);
158 if (val >= 1) {
159 if (eglSwapInterval(eglDisplayObject()->handle(), 1)) {
160 qCDebug(KWIN_CORE) << "Enabled v-sync";
161 }
162 } else {
163 qCWarning(KWIN_CORE) << "Cannot enable v-sync as max. swap interval is" << val;
164 }
165 } else {
166 /* In the GLX backend, we fall back to using glCopyPixels if we have no extension providing support for partial screen updates.
167 * However, that does not work in EGL - glCopyPixels with glDrawBuffer(GL_FRONT); does nothing.
168 * Hence we need EGL to preserve the backbuffer for us, so that we can draw the partial updates on it and call
169 * eglSwapBuffers() for each frame. eglSwapBuffers() then does the copy (no page flip possible in this mode),
170 * which means it is slow and not synced to the v-blank. */
171 qCWarning(KWIN_CORE) << "eglPostSubBufferNV not supported, have to enable buffer preservation - which breaks v-sync and performance";
172 eglSurfaceAttrib(eglDisplayObject()->handle(), surface(), EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);
173 }
174
175 m_swapStrategy = options->glPreferBufferSwap();
176 if (m_swapStrategy == Options::AutoSwapStrategy) {
177 // buffer copying is very fast with the nvidia blob
178 // but due to restrictions in DRI2 *incredibly* slow for all MESA drivers
179 // see https://www.x.org/releases/X11R7.7/doc/dri2proto/dri2proto.txt, item 2.5
180 if (GLPlatform::instance()->driver() == Driver_NVidia) {
181 m_swapStrategy = Options::CopyFrontBuffer;
182 } else if (GLPlatform::instance()->driver() != Driver_Unknown) { // undetected, finally resolved when context is initialized
183 m_swapStrategy = Options::ExtendDamage;
184 }
185 }
186}
187
189{
191 auto display = kwinApp()->outputBackend()->sceneEglDisplayObject();
192
193 // Use eglGetPlatformDisplayEXT() to get the display pointer
194 // if the implementation supports it.
195 if (!display) {
196 m_havePlatformBase = hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_base"));
197 if (m_havePlatformBase) {
198 // Make sure that the X11 platform is supported
199 if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_x11")) && !hasClientExtension(QByteArrayLiteral("EGL_KHR_platform_x11"))) {
200 qCWarning(KWIN_CORE) << "EGL_EXT_platform_base is supported, but neither EGL_EXT_platform_x11 nor EGL_KHR_platform_x11 is supported."
201 << "Cannot create EGLDisplay on X11";
202 return false;
203 }
204
205 m_backend->setEglDisplay(EglDisplay::create(eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT, m_backend->display(), nullptr)));
206 } else {
207 m_backend->setEglDisplay(EglDisplay::create(eglGetDisplay(m_backend->display())));
208 }
209 display = m_backend->sceneEglDisplayObject();
210 if (!display) {
211 qCWarning(KWIN_CORE) << "Failed to get the EGLDisplay";
212 return false;
213 }
214 }
215
216 setEglDisplay(display);
217
219 qCCritical(KWIN_CORE) << "Create OpenGL context failed";
220 return false;
221 }
222
223 if (!m_overlayWindow->create()) {
224 qCCritical(KWIN_X11STANDALONE) << "Could not get overlay window";
225 return false;
226 } else {
227 m_overlayWindow->setup(XCB_WINDOW_NONE);
228 }
229
230 EGLSurface surface = createSurface(m_overlayWindow->window());
231 if (surface == EGL_NO_SURFACE) {
232 qCCritical(KWIN_CORE) << "Creating egl surface failed";
233 return false;
234 }
236
237 if (!makeCurrent()) {
238 qCCritical(KWIN_CORE) << "Make Context Current failed";
239 return false;
240 }
241
242 EGLint error = eglGetError();
243 if (error != EGL_SUCCESS) {
244 qCWarning(KWIN_CORE) << "Error occurred while creating context " << error;
245 return false;
246 }
247
248 return true;
249}
250
251EGLSurface EglBackend::createSurface(xcb_window_t window)
252{
253 if (window == XCB_WINDOW_NONE) {
254 return EGL_NO_SURFACE;
255 }
256
257 // Window is 64 bits on a 64-bit architecture whereas xcb_window_t is always 32 bits.
258 ::Window nativeWindow = window;
259
260 EGLSurface surface = EGL_NO_SURFACE;
261 if (m_havePlatformBase) {
262 // eglCreatePlatformWindowSurfaceEXT() expects a pointer to the Window.
263 surface = eglCreatePlatformWindowSurfaceEXT(eglDisplayObject()->handle(), config(), (void *)&nativeWindow, nullptr);
264 } else {
265 // eglCreateWindowSurface() expects a Window, not a pointer to the Window. Use
266 // a c style cast as there are (buggy) platforms where the size of the Window
267 // type is not the same as the size of EGLNativeWindowType, reinterpret_cast<>()
268 // may not compile.
269 surface = eglCreateWindowSurface(eglDisplayObject()->handle(), config(), (EGLNativeWindowType)(uintptr_t)nativeWindow, nullptr);
270 }
271
272 return surface;
273}
274
276{
277 const EGLint config_attribs[] = {
278 EGL_SURFACE_TYPE,
279 EGL_WINDOW_BIT | (supportsBufferAge() ? 0 : EGL_SWAP_BEHAVIOR_PRESERVED_BIT),
280 EGL_RED_SIZE,
281 1,
282 EGL_GREEN_SIZE,
283 1,
284 EGL_BLUE_SIZE,
285 1,
286 EGL_ALPHA_SIZE,
287 0,
288 EGL_RENDERABLE_TYPE,
289 isOpenGLES() ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT,
290 EGL_CONFIG_CAVEAT,
291 EGL_NONE,
292 EGL_NONE,
293 };
294
295 EGLint count;
296 EGLConfig configs[1024];
297 if (eglChooseConfig(eglDisplayObject()->handle(), config_attribs, configs, 1024, &count) == EGL_FALSE) {
298 qCCritical(KWIN_CORE) << "choose config failed";
299 return EGL_NO_CONFIG_KHR;
300 }
301
302 UniqueCPtr<xcb_get_window_attributes_reply_t> attribs(xcb_get_window_attributes_reply(m_backend->connection(),
303 xcb_get_window_attributes_unchecked(m_backend->connection(), m_backend->rootWindow()),
304 nullptr));
305 if (!attribs) {
306 qCCritical(KWIN_CORE) << "Failed to get window attributes of root window";
307 return EGL_NO_CONFIG_KHR;
308 }
309
310 for (int i = 0; i < count; i++) {
311 EGLint val;
312 if (eglGetConfigAttrib(eglDisplayObject()->handle(), configs[i], EGL_NATIVE_VISUAL_ID, &val) == EGL_FALSE) {
313 qCCritical(KWIN_CORE) << "egl get config attrib failed";
314 }
315 if (uint32_t(val) == attribs->visual) {
316 return configs[i];
317 }
318 }
319 return configs[0];
320}
321
322void EglBackend::screenGeometryChanged()
323{
324 overlayWindow()->resize(workspace()->geometry().size());
325
326 // The back buffer contents are now undefined
327 m_bufferAge = 0;
328 m_fbo = std::make_unique<GLFramebuffer>(0, workspace()->geometry().size());
329}
330
332{
333 makeCurrent();
334
335 QRegion repaint;
336 if (supportsBufferAge()) {
337 repaint = m_damageJournal.accumulate(m_bufferAge, infiniteRegion());
338 }
339 eglWaitNative(EGL_CORE_NATIVE_ENGINE);
340
341 if (!m_query) {
342 m_query = std::make_unique<GLRenderTimeQuery>();
343 }
344 m_query->begin();
346 .renderTarget = RenderTarget(m_fbo.get()),
347 .repaint = repaint,
348 };
349}
350
351void EglBackend::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
352{
353 m_query->end();
354 // Save the damaged region to history
355 if (supportsBufferAge()) {
356 m_damageJournal.add(damagedRegion);
357 }
358 m_lastRenderedRegion = renderedRegion;
359}
360
361void EglBackend::present(Output *output, const std::shared_ptr<OutputFrame> &frame)
362{
363 m_frame = frame;
364 // Start the software vsync monitor. There is no any reliable way to determine when
365 // eglSwapBuffers() or eglSwapBuffersWithDamageEXT() completes.
366 m_vsyncMonitor->arm();
367
368 QRegion effectiveRenderedRegion = m_lastRenderedRegion;
369 if (!GLPlatform::instance()->isGLES()) {
370 const QRect displayRect = workspace()->geometry();
371 if (!supportsBufferAge() && m_swapStrategy == Options::CopyFrontBuffer && m_lastRenderedRegion != displayRect) {
372 glReadBuffer(GL_FRONT);
373 copyPixels(QRegion(displayRect) - m_lastRenderedRegion, displayRect.size());
374 glReadBuffer(GL_BACK);
375 effectiveRenderedRegion = displayRect;
376 }
377 }
378
379 presentSurface(surface(), effectiveRenderedRegion, workspace()->geometry());
380
381 if (overlayWindow() && overlayWindow()->window()) { // show the window only after the first pass,
382 overlayWindow()->show(); // since that pass may take long
383 }
384}
385
386void EglBackend::presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry)
387{
388 const bool fullRepaint = supportsBufferAge() || (damage == screenGeometry);
389
390 if (fullRepaint || !m_havePostSubBuffer) {
391 // the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation)
392 eglSwapBuffers(eglDisplayObject()->handle(), surface);
393 if (supportsBufferAge()) {
394 eglQuerySurface(eglDisplayObject()->handle(), surface, EGL_BUFFER_AGE_EXT, &m_bufferAge);
395 }
396 } else {
397 // a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area
398 for (const QRect &r : damage) {
399 eglPostSubBufferNV(eglDisplayObject()->handle(), surface, r.left(), screenGeometry.height() - r.bottom() - 1, r.width(), r.height());
400 }
401 }
402}
403
405{
406 return m_overlayWindow.get();
407}
408
410{
411 return m_layer.get();
412}
413
414std::chrono::nanoseconds EglBackend::queryRenderTime()
415{
416 makeCurrent();
417 return m_query->result();
418}
419
420void EglBackend::vblank(std::chrono::nanoseconds timestamp)
421{
422 m_frame->presented(std::chrono::nanoseconds::zero(), timestamp, queryRenderTime(), PresentationMode::VSync);
423 m_frame.reset();
424}
425
430
432{
433 auto texture = std::make_shared<EglPixmapTexture>(static_cast<EglBackend *>(m_backend));
434 if (texture->create(m_pixmap)) {
435 m_texture = {texture};
436 return true;
437 } else {
438 return false;
439 }
440}
441
442void EglSurfaceTextureX11::update(const QRegion &region)
443{
444 // mipmaps need to be updated
446}
447
449 : GLTexture(GL_TEXTURE_2D)
450 , m_backend(backend)
451{
452}
453
455{
456 if (m_image != EGL_NO_IMAGE_KHR) {
457 eglDestroyImageKHR(m_backend->eglDisplayObject()->handle(), m_image);
458 }
459}
460
462{
463 const xcb_pixmap_t nativePixmap = pixmap->pixmap();
464 if (nativePixmap == XCB_NONE) {
465 return false;
466 }
467
468 glGenTextures(1, &d->m_texture);
469 setWrapMode(GL_CLAMP_TO_EDGE);
470 setFilter(GL_LINEAR);
471 bind();
472 const EGLint attribs[] = {
473 EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
474 EGL_NONE};
475 m_image = eglCreateImageKHR(m_backend->eglDisplayObject()->handle(),
476 EGL_NO_CONTEXT,
477 EGL_NATIVE_PIXMAP_KHR,
478 reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(nativePixmap)),
479 attribs);
480
481 if (EGL_NO_IMAGE_KHR == m_image) {
482 qCDebug(KWIN_X11STANDALONE) << "failed to create egl image";
483 unbind();
484 return false;
485 }
486 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(m_image));
487 unbind();
489 d->m_size = pixmap->size();
490 d->updateMatrix();
491 return true;
492}
493
494void EglPixmapTexture::onDamage()
495{
496 if (options->isGlStrictBinding()) {
497 // This is just implemented to be consistent with
498 // the example in mesa/demos/src/egl/opengles1/texture_from_pixmap.c
499 eglWaitNative(EGL_CORE_NATIVE_ENGINE);
500 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(m_image));
501 }
502}
503
504} // namespace KWin
505
506#include "moc_x11_standalone_egl_backend.cpp"
bool hasClientExtension(const QByteArray &ext) const
EglDisplay * eglDisplayObject() const
void setEglDisplay(EglDisplay *display)
void setSurface(const EGLSurface &surface)
bool createContext(EGLConfig config)
void add(const QRegion &region)
QRegion accumulate(int bufferAge, const QRegion &fallback=QRegion()) const
EglBackend(::Display *display, X11StandaloneBackend *platform)
std::chrono::nanoseconds queryRenderTime()
OverlayWindow * overlayWindow() const override
void present(Output *output, const std::shared_ptr< OutputFrame > &frame) override
void endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
std::unique_ptr< SurfaceTexture > createSurfaceTextureX11(SurfacePixmapX11 *texture) override
OutputLayerBeginFrameInfo beginFrame()
OutputLayer * primaryLayer(Output *output) override
static std::unique_ptr< EglDisplay > create(::EGLDisplay display, bool owning=true)
::EGLDisplay handle() const
EglLayer(EglBackend *backend)
std::chrono::nanoseconds queryRenderTime() const override
std::optional< OutputLayerBeginFrameInfo > beginFrame() override
bool endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override
EglSurfaceTextureX11(EglBackend *backend, SurfacePixmapX11 *texture)
void update(const QRegion &region) override
static GLPlatform * instance()
Definition glplatform.h:394
const std::unique_ptr< GLTexturePrivate > d
Definition gltexture.h:143
void setContentTransform(OutputTransform transform)
void setFilter(GLenum filter)
void setWrapMode(GLenum mode)
bool isFailed() const
Whether the creation of the Backend failed.
void setFailed(const QString &reason)
Sets the backend initialization to failed.
void copyPixels(const QRegion &region, const QSize &screenSize)
bool hasExtension(const QByteArray &extension) const
bool supportsBufferAge() const
OpenGLSurfaceContents m_texture
OpenGLSurfaceContents texture() const
GlSwapStrategy glPreferBufferSwap
Definition options.h:197
bool isGlStrictBinding() const
Definition options.h:657
virtual void show()=0
virtual void resize(const QSize &size)=0
int refreshRate() const
void refreshRateChanged()
static RenderLoopPrivate * get(RenderLoop *loop)
static std::unique_ptr< SoftwareVsyncMonitor > create()
xcb_pixmap_t pixmap() const
void vblankOccurred(std::chrono::nanoseconds timestamp)
QRect geometry() const
void geometryChanged()
xcb_connection_t * connection() const
EglDisplay * sceneEglDisplayObject() const override
void setEglDisplay(std::unique_ptr< EglDisplay > &&display)
void * EGLClientBuffer
KWIN_EXPORT QRect infiniteRegion()
Definition globals.h:234
@ Driver_NVidia
Definition glplatform.h:54
@ Driver_Unknown
Definition glplatform.h:68
Workspace * workspace()
Definition workspace.h:830
bool hasGLExtension(const QByteArray &extension)
Definition glutils.cpp:138
Options * options
Definition main.cpp:73
std::unique_ptr< T, CDeleter > UniqueCPtr
Definition c_ptr.h:28
struct _XDisplay Display