1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2010, 2012 Martin Gräßlin <mgraesslin@kde.org> |
6 | |
7 | This program is free software; you can redistribute it and/or modify |
8 | it under the terms of the GNU General Public License as published by |
9 | the Free Software Foundation; either version 2 of the License, or |
10 | (at your option) any later version. |
11 | |
12 | This program is distributed in the hope that it will be useful, |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | GNU General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU General Public License |
18 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | *********************************************************************/ |
20 | #include "eglonxbackend.h" |
21 | // kwin |
22 | #include "options.h" |
23 | #include "overlaywindow.h" |
24 | #include "xcbutils.h" |
25 | // kwin libs |
26 | #include <kwinglplatform.h> |
27 | // KDE |
28 | #include <KDE/KDebug> |
29 | // system |
30 | #include <unistd.h> |
31 | |
32 | namespace KWin |
33 | { |
34 | |
35 | EglOnXBackend::EglOnXBackend() |
36 | : OpenGLBackend() |
37 | , ctx(EGL_NO_CONTEXT) |
38 | , surfaceHasSubPost(0) |
39 | , m_bufferAge(0) |
40 | { |
41 | init(); |
42 | // Egl is always direct rendering |
43 | setIsDirectRendering(true); |
44 | } |
45 | |
46 | EglOnXBackend::~EglOnXBackend() |
47 | { |
48 | cleanupGL(); |
49 | checkGLError("Cleanup" ); |
50 | eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
51 | eglDestroyContext(dpy, ctx); |
52 | eglDestroySurface(dpy, surface); |
53 | eglTerminate(dpy); |
54 | eglReleaseThread(); |
55 | if (overlayWindow()->window()) { |
56 | overlayWindow()->destroy(); |
57 | } |
58 | } |
59 | |
60 | static bool gs_tripleBufferUndetected = true; |
61 | static bool gs_tripleBufferNeedsDetection = false; |
62 | |
63 | void EglOnXBackend::init() |
64 | { |
65 | if (!initRenderingContext()) { |
66 | setFailed("Could not initialize rendering context" ); |
67 | return; |
68 | } |
69 | |
70 | initEGL(); |
71 | if (!hasGLExtension("EGL_KHR_image" ) && |
72 | (!hasGLExtension("EGL_KHR_image_base" ) || |
73 | !hasGLExtension("EGL_KHR_image_pixmap" ))) { |
74 | setFailed("Required support for binding pixmaps to EGLImages not found, disabling compositing" ); |
75 | return; |
76 | } |
77 | GLPlatform *glPlatform = GLPlatform::instance(); |
78 | glPlatform->detect(EglPlatformInterface); |
79 | if (GLPlatform::instance()->driver() == Driver_Intel) |
80 | options->setUnredirectFullscreen(false); // bug #252817 |
81 | options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting |
82 | if (options->glPreferBufferSwap() == Options::AutoSwapStrategy) |
83 | options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen |
84 | glPlatform->printResults(); |
85 | initGL(EglPlatformInterface); |
86 | if (!hasGLExtension("GL_OES_EGL_image" )) { |
87 | setFailed("Required extension GL_OES_EGL_image not found, disabling compositing" ); |
88 | return; |
89 | } |
90 | |
91 | // check for EGL_NV_post_sub_buffer and whether it can be used on the surface |
92 | if (eglPostSubBufferNV) { |
93 | if (eglQuerySurface(dpy, surface, EGL_POST_SUB_BUFFER_SUPPORTED_NV, &surfaceHasSubPost) == EGL_FALSE) { |
94 | EGLint error = eglGetError(); |
95 | if (error != EGL_SUCCESS && error != EGL_BAD_ATTRIBUTE) { |
96 | setFailed("query surface failed" ); |
97 | return; |
98 | } else { |
99 | surfaceHasSubPost = EGL_FALSE; |
100 | } |
101 | } |
102 | } |
103 | |
104 | setSupportsBufferAge(false); |
105 | |
106 | if (hasGLExtension("EGL_EXT_buffer_age" )) { |
107 | const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE" ); |
108 | |
109 | if (useBufferAge != "0" ) |
110 | setSupportsBufferAge(true); |
111 | } |
112 | |
113 | setSyncsToVBlank(false); |
114 | setBlocksForRetrace(false); |
115 | gs_tripleBufferNeedsDetection = false; |
116 | m_swapProfiler.init(); |
117 | if (surfaceHasSubPost) { |
118 | kDebug(1212) << "EGL implementation and surface support eglPostSubBufferNV, let's use it" ; |
119 | |
120 | if (options->glPreferBufferSwap() != Options::NoSwapEncourage) { |
121 | // check if swap interval 1 is supported |
122 | EGLint val; |
123 | eglGetConfigAttrib(dpy, config, EGL_MAX_SWAP_INTERVAL, &val); |
124 | if (val >= 1) { |
125 | if (eglSwapInterval(dpy, 1)) { |
126 | kDebug(1212) << "Enabled v-sync" ; |
127 | setSyncsToVBlank(true); |
128 | const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER" ); |
129 | if (!tripleBuffer.isEmpty()) { |
130 | setBlocksForRetrace(qstrcmp(tripleBuffer, "0" ) == 0); |
131 | gs_tripleBufferUndetected = false; |
132 | } |
133 | gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected; |
134 | } |
135 | } else { |
136 | kWarning(1212) << "Cannot enable v-sync as max. swap interval is" << val; |
137 | } |
138 | } else { |
139 | // disable v-sync |
140 | eglSwapInterval(dpy, 0); |
141 | } |
142 | } else { |
143 | /* In the GLX backend, we fall back to using glCopyPixels if we have no extension providing support for partial screen updates. |
144 | * However, that does not work in EGL - glCopyPixels with glDrawBuffer(GL_FRONT); does nothing. |
145 | * Hence we need EGL to preserve the backbuffer for us, so that we can draw the partial updates on it and call |
146 | * eglSwapBuffers() for each frame. eglSwapBuffers() then does the copy (no page flip possible in this mode), |
147 | * which means it is slow and not synced to the v-blank. */ |
148 | kWarning(1212) << "eglPostSubBufferNV not supported, have to enable buffer preservation - which breaks v-sync and performance" ; |
149 | eglSurfaceAttrib(dpy, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); |
150 | } |
151 | } |
152 | |
153 | bool EglOnXBackend::initRenderingContext() |
154 | { |
155 | dpy = eglGetDisplay(display()); |
156 | if (dpy == EGL_NO_DISPLAY) |
157 | return false; |
158 | |
159 | EGLint major, minor; |
160 | if (eglInitialize(dpy, &major, &minor) == EGL_FALSE) |
161 | return false; |
162 | |
163 | #ifdef KWIN_HAVE_OPENGLES |
164 | eglBindAPI(EGL_OPENGL_ES_API); |
165 | #else |
166 | if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { |
167 | kError(1212) << "bind OpenGL API failed" ; |
168 | return false; |
169 | } |
170 | #endif |
171 | |
172 | initBufferConfigs(); |
173 | |
174 | if (!overlayWindow()->create()) { |
175 | kError(1212) << "Could not get overlay window" ; |
176 | return false; |
177 | } else { |
178 | overlayWindow()->setup(None); |
179 | } |
180 | |
181 | surface = eglCreateWindowSurface(dpy, config, overlayWindow()->window(), 0); |
182 | |
183 | #ifdef KWIN_HAVE_OPENGLES |
184 | const EGLint context_attribs[] = { |
185 | EGL_CONTEXT_CLIENT_VERSION, 2, |
186 | EGL_NONE |
187 | }; |
188 | |
189 | ctx = eglCreateContext(dpy, config, EGL_NO_CONTEXT, context_attribs); |
190 | #else |
191 | const EGLint context_attribs_31_core[] = { |
192 | EGL_CONTEXT_MAJOR_VERSION_KHR, 3, |
193 | EGL_CONTEXT_MINOR_VERSION_KHR, 1, |
194 | EGL_NONE |
195 | }; |
196 | |
197 | const EGLint context_attribs_legacy[] = { |
198 | EGL_NONE |
199 | }; |
200 | |
201 | const QByteArray eglExtensions = eglQueryString(dpy, EGL_EXTENSIONS); |
202 | const QList<QByteArray> extensions = eglExtensions.split(' '); |
203 | |
204 | // Try to create a 3.1 core context |
205 | if (options->glCoreProfile() && extensions.contains("EGL_KHR_create_context" )) |
206 | ctx = eglCreateContext(dpy, config, EGL_NO_CONTEXT, context_attribs_31_core); |
207 | |
208 | if (ctx == EGL_NO_CONTEXT) |
209 | ctx = eglCreateContext(dpy, config, EGL_NO_CONTEXT, context_attribs_legacy); |
210 | #endif |
211 | |
212 | if (ctx == EGL_NO_CONTEXT) { |
213 | kError(1212) << "Create Context failed" ; |
214 | return false; |
215 | } |
216 | |
217 | if (eglMakeCurrent(dpy, surface, surface, ctx) == EGL_FALSE) { |
218 | kError(1212) << "Make Context Current failed" ; |
219 | return false; |
220 | } |
221 | |
222 | kDebug(1212) << "EGL version: " << major << "." << minor; |
223 | |
224 | EGLint error = eglGetError(); |
225 | if (error != EGL_SUCCESS) { |
226 | kWarning(1212) << "Error occurred while creating context " << error; |
227 | return false; |
228 | } |
229 | |
230 | return true; |
231 | } |
232 | |
233 | bool EglOnXBackend::initBufferConfigs() |
234 | { |
235 | const EGLint config_attribs[] = { |
236 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT|EGL_SWAP_BEHAVIOR_PRESERVED_BIT, |
237 | EGL_RED_SIZE, 1, |
238 | EGL_GREEN_SIZE, 1, |
239 | EGL_BLUE_SIZE, 1, |
240 | EGL_ALPHA_SIZE, 0, |
241 | #ifdef KWIN_HAVE_OPENGLES |
242 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, |
243 | #else |
244 | EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, |
245 | #endif |
246 | EGL_CONFIG_CAVEAT, EGL_NONE, |
247 | EGL_NONE, |
248 | }; |
249 | |
250 | EGLint count; |
251 | EGLConfig configs[1024]; |
252 | if (eglChooseConfig(dpy, config_attribs, configs, 1024, &count) == EGL_FALSE) { |
253 | kError(1212) << "choose config failed" ; |
254 | return false; |
255 | } |
256 | |
257 | Xcb::WindowAttributes attribs(rootWindow()); |
258 | if (!attribs) { |
259 | kError(1212) << "Failed to get window attributes of root window" ; |
260 | return false; |
261 | } |
262 | |
263 | config = configs[0]; |
264 | for (int i = 0; i < count; i++) { |
265 | EGLint val; |
266 | if (eglGetConfigAttrib(dpy, configs[i], EGL_NATIVE_VISUAL_ID, &val) == EGL_FALSE) { |
267 | kError(1212) << "egl get config attrib failed" ; |
268 | } |
269 | if (uint32_t(val) == attribs->visual) { |
270 | config = configs[i]; |
271 | break; |
272 | } |
273 | } |
274 | return true; |
275 | } |
276 | |
277 | void EglOnXBackend::present() |
278 | { |
279 | if (lastDamage().isEmpty()) |
280 | return; |
281 | |
282 | const QRegion displayRegion(0, 0, displayWidth(), displayHeight()); |
283 | const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion); |
284 | |
285 | if (fullRepaint || !surfaceHasSubPost) { |
286 | if (gs_tripleBufferNeedsDetection) { |
287 | eglWaitGL(); |
288 | m_swapProfiler.begin(); |
289 | } |
290 | // the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation) |
291 | eglSwapBuffers(dpy, surface); |
292 | if (gs_tripleBufferNeedsDetection) { |
293 | eglWaitGL(); |
294 | if (char result = m_swapProfiler.end()) { |
295 | gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false; |
296 | if (result == 'd' && GLPlatform::instance()->driver() == Driver_NVidia) { |
297 | // TODO this is a workaround, we should get __GL_YIELD set before libGL checks it |
298 | if (qstrcmp(qgetenv("__GL_YIELD" ), "USLEEP" )) { |
299 | options->setGlPreferBufferSwap(0); |
300 | eglSwapInterval(dpy, 0); |
301 | kWarning(1212) << "\nIt seems you are using the nvidia driver without triple buffering\n" |
302 | "You must export __GL_YIELD=\"USLEEP\" to prevent large CPU overhead on synced swaps\n" |
303 | "Preferably, enable the TripleBuffer Option in the xorg.conf Device\n" |
304 | "For this reason, the tearing prevention has been disabled.\n" |
305 | "See https://bugs.kde.org/show_bug.cgi?id=322060\n" ; |
306 | } |
307 | } |
308 | setBlocksForRetrace(result == 'd'); |
309 | } |
310 | } |
311 | if (supportsBufferAge()) { |
312 | eglQuerySurface(dpy, surface, EGL_BUFFER_AGE_EXT, &m_bufferAge); |
313 | } |
314 | } else { |
315 | // a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area |
316 | foreach (const QRect & r, lastDamage().rects()) { |
317 | eglPostSubBufferNV(dpy, surface, r.left(), displayHeight() - r.bottom() - 1, r.width(), r.height()); |
318 | } |
319 | } |
320 | |
321 | setLastDamage(QRegion()); |
322 | if (!supportsBufferAge()) { |
323 | eglWaitGL(); |
324 | xcb_flush(connection()); |
325 | } |
326 | } |
327 | |
328 | void EglOnXBackend::screenGeometryChanged(const QSize &size) |
329 | { |
330 | Q_UNUSED(size) |
331 | |
332 | // TODO: base implementation in OpenGLBackend |
333 | |
334 | // The back buffer contents are now undefined |
335 | m_bufferAge = 0; |
336 | } |
337 | |
338 | SceneOpenGL::TexturePrivate *EglOnXBackend::createBackendTexture(SceneOpenGL::Texture *texture) |
339 | { |
340 | return new EglTexture(texture, this); |
341 | } |
342 | |
343 | QRegion EglOnXBackend::prepareRenderingFrame() |
344 | { |
345 | QRegion repaint; |
346 | |
347 | if (gs_tripleBufferNeedsDetection) { |
348 | // the composite timer floors the repaint frequency. This can pollute our triple buffering |
349 | // detection because the glXSwapBuffers call for the new frame has to wait until the pending |
350 | // one scanned out. |
351 | // So we compensate for that by waiting an extra milisecond to give the driver the chance to |
352 | // fllush the buffer queue |
353 | usleep(1000); |
354 | } |
355 | |
356 | present(); |
357 | |
358 | if (supportsBufferAge()) |
359 | repaint = accumulatedDamageHistory(m_bufferAge); |
360 | |
361 | startRenderTimer(); |
362 | eglWaitNative(EGL_CORE_NATIVE_ENGINE); |
363 | |
364 | return repaint; |
365 | } |
366 | |
367 | void EglOnXBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) |
368 | { |
369 | if (damagedRegion.isEmpty()) { |
370 | setLastDamage(QRegion()); |
371 | |
372 | // If the damaged region of a window is fully occluded, the only |
373 | // rendering done, if any, will have been to repair a reused back |
374 | // buffer, making it identical to the front buffer. |
375 | // |
376 | // In this case we won't post the back buffer. Instead we'll just |
377 | // set the buffer age to 1, so the repaired regions won't be |
378 | // rendered again in the next frame. |
379 | if (!renderedRegion.isEmpty()) |
380 | glFlush(); |
381 | |
382 | m_bufferAge = 1; |
383 | return; |
384 | } |
385 | |
386 | setLastDamage(renderedRegion); |
387 | |
388 | if (!blocksForRetrace()) { |
389 | // This also sets lastDamage to empty which prevents the frame from |
390 | // being posted again when prepareRenderingFrame() is called. |
391 | present(); |
392 | } else { |
393 | // Make sure that the GPU begins processing the command stream |
394 | // now and not the next time prepareRenderingFrame() is called. |
395 | glFlush(); |
396 | } |
397 | |
398 | if (overlayWindow()->window()) // show the window only after the first pass, |
399 | overlayWindow()->show(); // since that pass may take long |
400 | |
401 | // Save the damaged region to history |
402 | if (supportsBufferAge()) |
403 | addToDamageHistory(damagedRegion); |
404 | } |
405 | |
406 | /************************************************ |
407 | * EglTexture |
408 | ************************************************/ |
409 | |
410 | EglTexture::EglTexture(KWin::SceneOpenGL::Texture *texture, KWin::EglOnXBackend *backend) |
411 | : SceneOpenGL::TexturePrivate() |
412 | , q(texture) |
413 | , m_backend(backend) |
414 | , m_image(EGL_NO_IMAGE_KHR) |
415 | { |
416 | m_target = GL_TEXTURE_2D; |
417 | } |
418 | |
419 | EglTexture::~EglTexture() |
420 | { |
421 | if (m_image != EGL_NO_IMAGE_KHR) { |
422 | eglDestroyImageKHR(m_backend->dpy, m_image); |
423 | } |
424 | } |
425 | |
426 | OpenGLBackend *EglTexture::backend() |
427 | { |
428 | return m_backend; |
429 | } |
430 | |
431 | void EglTexture::findTarget() |
432 | { |
433 | if (m_target != GL_TEXTURE_2D) { |
434 | m_target = GL_TEXTURE_2D; |
435 | } |
436 | } |
437 | |
438 | bool EglTexture::loadTexture(const Pixmap &pix, const QSize &size, int depth) |
439 | { |
440 | Q_UNUSED(depth) |
441 | if (pix == None) |
442 | return false; |
443 | |
444 | glGenTextures(1, &m_texture); |
445 | q->setWrapMode(GL_CLAMP_TO_EDGE); |
446 | q->setFilter(GL_LINEAR); |
447 | q->bind(); |
448 | const EGLint attribs[] = { |
449 | EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, |
450 | EGL_NONE |
451 | }; |
452 | m_image = eglCreateImageKHR(m_backend->dpy, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR, |
453 | (EGLClientBuffer)pix, attribs); |
454 | |
455 | if (EGL_NO_IMAGE_KHR == m_image) { |
456 | kDebug(1212) << "failed to create egl image" ; |
457 | q->unbind(); |
458 | q->discard(); |
459 | return false; |
460 | } |
461 | glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)m_image); |
462 | q->unbind(); |
463 | checkGLError("load texture" ); |
464 | q->setYInverted(true); |
465 | m_size = size; |
466 | updateMatrix(); |
467 | return true; |
468 | } |
469 | |
470 | void KWin::EglTexture::onDamage() |
471 | { |
472 | if (options->isGlStrictBinding()) { |
473 | // This is just implemented to be consistent with |
474 | // the example in mesa/demos/src/egl/opengles1/texture_from_pixmap.c |
475 | eglWaitNative(EGL_CORE_NATIVE_ENGINE); |
476 | glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) m_image); |
477 | } |
478 | GLTexturePrivate::onDamage(); |
479 | } |
480 | |
481 | } // namespace |
482 | |