Changeset View
Standalone View
plugins/platforms/x11/standalone/glxbackend.cpp
Show All 24 Lines | |||||
25 | // own | 25 | // own | ||
26 | #include "glxbackend.h" | 26 | #include "glxbackend.h" | ||
27 | #include "logging.h" | 27 | #include "logging.h" | ||
28 | #include "glx_context_attribute_builder.h" | 28 | #include "glx_context_attribute_builder.h" | ||
29 | // kwin | 29 | // kwin | ||
30 | #include "options.h" | 30 | #include "options.h" | ||
31 | #include "overlaywindow.h" | 31 | #include "overlaywindow.h" | ||
32 | #include "composite.h" | 32 | #include "composite.h" | ||
33 | #include "perf.h" | ||||
33 | #include "platform.h" | 34 | #include "platform.h" | ||
34 | #include "scene.h" | 35 | #include "scene.h" | ||
35 | #include "screens.h" | 36 | #include "screens.h" | ||
36 | #include "xcbutils.h" | 37 | #include "xcbutils.h" | ||
37 | #include "texture.h" | 38 | #include "texture.h" | ||
38 | // kwin libs | 39 | // kwin libs | ||
39 | #include <kwinglplatform.h> | 40 | #include <kwinglplatform.h> | ||
40 | #include <kwinglutils.h> | 41 | #include <kwinglutils.h> | ||
▲ Show 20 Lines • Show All 53 Lines • ▼ Show 20 Line(s) | 88 | { | |||
94 | if (ev->drawable == m_drawable || ev->drawable == m_glxDrawable) { | 95 | if (ev->drawable == m_drawable || ev->drawable == m_glxDrawable) { | ||
95 | Compositor::self()->bufferSwapComplete(); | 96 | Compositor::self()->bufferSwapComplete(); | ||
96 | return true; | 97 | return true; | ||
97 | } | 98 | } | ||
98 | 99 | | |||
99 | return false; | 100 | return false; | ||
100 | } | 101 | } | ||
101 | 102 | | |||
102 | | ||||
103 | // ----------------------------------------------------------------------- | | |||
104 | | ||||
105 | | ||||
106 | | ||||
107 | GlxBackend::GlxBackend(Display *display) | 103 | GlxBackend::GlxBackend(Display *display) | ||
108 | : OpenGLBackend() | 104 | : OpenGLBackend() | ||
109 | , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) | 105 | , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) | ||
110 | , window(None) | 106 | , window(None) | ||
111 | , fbconfig(NULL) | 107 | , fbconfig(NULL) | ||
112 | , glxWindow(None) | 108 | , glxWindow(None) | ||
113 | , ctx(nullptr) | 109 | , ctx(nullptr) | ||
114 | , m_bufferAge(0) | 110 | , m_bufferAge(0) | ||
115 | , haveSwapInterval(false) | | |||
116 | , m_x11Display(display) | 111 | , m_x11Display(display) | ||
117 | { | 112 | { | ||
118 | // Ensures calls to glXSwapBuffers will always block until the next | | |||
119 | // retrace when using the proprietary NVIDIA driver. This must be | | |||
120 | // set before libGL.so is loaded. | | |||
121 | setenv("__GL_MaxFramesAllowed", "1", true); | | |||
122 | | ||||
123 | // Force initialization of GLX integration in the Qt's xcb backend | 113 | // Force initialization of GLX integration in the Qt's xcb backend | ||
124 | // to make it call XESetWireToEvent callbacks, which is required | 114 | // to make it call XESetWireToEvent callbacks, which is required | ||
125 | // by Mesa when using DRI2. | 115 | // by Mesa when using DRI2. | ||
126 | QOpenGLContext::supportsThreadedOpenGL(); | 116 | QOpenGLContext::supportsThreadedOpenGL(); | ||
127 | } | 117 | } | ||
128 | 118 | | |||
129 | static bool gs_tripleBufferUndetected = true; | | |||
130 | static bool gs_tripleBufferNeedsDetection = false; | | |||
131 | | ||||
132 | GlxBackend::~GlxBackend() | 119 | GlxBackend::~GlxBackend() | ||
133 | { | 120 | { | ||
134 | if (isFailed()) { | 121 | if (isFailed()) { | ||
135 | m_overlayWindow->destroy(); | 122 | m_overlayWindow->destroy(); | ||
136 | } | 123 | } | ||
137 | // TODO: cleanup in error case | 124 | // TODO: cleanup in error case | ||
138 | // do cleanup after initBuffer() | 125 | // do cleanup after initBuffer() | ||
139 | cleanupGL(); | 126 | cleanupGL(); | ||
140 | doneCurrent(); | 127 | doneCurrent(); | ||
141 | 128 | | |||
142 | gs_tripleBufferUndetected = true; | | |||
143 | gs_tripleBufferNeedsDetection = false; | | |||
144 | | ||||
145 | if (ctx) | 129 | if (ctx) | ||
146 | glXDestroyContext(display(), ctx); | 130 | glXDestroyContext(display(), ctx); | ||
147 | 131 | | |||
148 | if (glxWindow) | 132 | if (glxWindow) | ||
149 | glXDestroyWindow(display(), glxWindow); | 133 | glXDestroyWindow(display(), glxWindow); | ||
150 | 134 | | |||
151 | if (window) | 135 | if (window) | ||
152 | XDestroyWindow(display(), window); | 136 | XDestroyWindow(display(), window); | ||
Show All 14 Lines | 150 | #if HAVE_EPOXY_GLX | |||
167 | ret = glXGetProcAddress((const GLubyte*) name); | 151 | ret = glXGetProcAddress((const GLubyte*) name); | ||
168 | #endif | 152 | #endif | ||
169 | #if HAVE_DL_LIBRARY | 153 | #if HAVE_DL_LIBRARY | ||
170 | if (ret == nullptr) | 154 | if (ret == nullptr) | ||
171 | ret = (glXFuncPtr) dlsym(RTLD_DEFAULT, name); | 155 | ret = (glXFuncPtr) dlsym(RTLD_DEFAULT, name); | ||
172 | #endif | 156 | #endif | ||
173 | return ret; | 157 | return ret; | ||
174 | } | 158 | } | ||
175 | glXSwapIntervalMESA_func glXSwapIntervalMESA; | | |||
176 | 159 | | |||
177 | void GlxBackend::init() | 160 | void GlxBackend::init() | ||
178 | { | 161 | { | ||
179 | // Require at least GLX 1.3 | 162 | // Require at least GLX 1.3 | ||
180 | if (!checkVersion()) { | 163 | if (!checkVersion()) { | ||
181 | setFailed(QStringLiteral("Requires at least GLX 1.3")); | 164 | setFailed(QStringLiteral("Requires at least GLX 1.3")); | ||
182 | return; | 165 | return; | ||
183 | } | 166 | } | ||
184 | 167 | | |||
185 | initExtensions(); | 168 | initExtensions(); | ||
186 | | ||||
187 | // resolve glXSwapIntervalMESA if available | | |||
188 | if (hasExtension(QByteArrayLiteral("GLX_MESA_swap_control"))) { | | |||
189 | glXSwapIntervalMESA = (glXSwapIntervalMESA_func) getProcAddress("glXSwapIntervalMESA"); | | |||
190 | } else { | | |||
191 | glXSwapIntervalMESA = nullptr; | | |||
192 | } | | |||
193 | | ||||
194 | initVisualDepthHashTable(); | 169 | initVisualDepthHashTable(); | ||
195 | 170 | | |||
196 | if (!initBuffer()) { | 171 | if (!initBuffer()) { | ||
197 | setFailed(QStringLiteral("Could not initialize the buffer")); | 172 | setFailed(QStringLiteral("Could not initialize the buffer")); | ||
198 | return; | 173 | return; | ||
199 | } | 174 | } | ||
200 | 175 | | |||
201 | if (!initRenderingContext()) { | 176 | if (!initRenderingContext()) { | ||
202 | setFailed(QStringLiteral("Could not initialize rendering context")); | 177 | setFailed(QStringLiteral("Could not initialize rendering context")); | ||
203 | return; | 178 | return; | ||
204 | } | 179 | } | ||
205 | 180 | | |||
206 | // Initialize OpenGL | 181 | // Initialize OpenGL | ||
207 | GLPlatform *glPlatform = GLPlatform::instance(); | 182 | GLPlatform *glPlatform = GLPlatform::instance(); | ||
208 | glPlatform->detect(GlxPlatformInterface); | 183 | glPlatform->detect(GlxPlatformInterface); | ||
209 | options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting | 184 | options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting | ||
210 | if (options->glPreferBufferSwap() == Options::AutoSwapStrategy) | 185 | if (options->glPreferBufferSwap() == Options::AutoSwapStrategy) | ||
211 | options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen | 186 | options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen | ||
212 | glPlatform->printResults(); | 187 | glPlatform->printResults(); | ||
213 | initGL(&getProcAddress); | 188 | initGL(&getProcAddress); | ||
214 | 189 | | |||
215 | // Check whether certain features are supported | 190 | // Check whether certain features are supported | ||
191 | m_haveOMLSyncControl = hasExtension(QByteArrayLiteral("GLX_OML_sync_control")); | ||||
216 | m_haveMESACopySubBuffer = hasExtension(QByteArrayLiteral("GLX_MESA_copy_sub_buffer")); | 192 | m_haveMESACopySubBuffer = hasExtension(QByteArrayLiteral("GLX_MESA_copy_sub_buffer")); | ||
217 | m_haveMESASwapControl = hasExtension(QByteArrayLiteral("GLX_MESA_swap_control")); | 193 | | ||
218 | m_haveEXTSwapControl = hasExtension(QByteArrayLiteral("GLX_EXT_swap_control")); | | |||
219 | m_haveSGISwapControl = hasExtension(QByteArrayLiteral("GLX_SGI_swap_control")); | | |||
220 | // only enable Intel swap event if env variable is set, see BUG 342582 | 194 | // only enable Intel swap event if env variable is set, see BUG 342582 | ||
221 | m_haveINTELSwapEvent = hasExtension(QByteArrayLiteral("GLX_INTEL_swap_event")) | 195 | m_haveINTELSwapEvent = hasExtension(QByteArrayLiteral("GLX_INTEL_swap_event")) | ||
222 | && qgetenv("KWIN_USE_INTEL_SWAP_EVENT") == QByteArrayLiteral("1"); | 196 | && qgetenv("KWIN_USE_INTEL_SWAP_EVENT") == QByteArrayLiteral("1"); | ||
223 | 197 | | |||
224 | if (m_haveINTELSwapEvent) { | 198 | qDebug() << "\nGlxBackend::init" << m_haveOMLSyncControl | ||
199 | << m_haveMESACopySubBuffer << m_haveINTELSwapEvent; | ||||
200 | | ||||
201 | if (hasSwapEvent()) { | ||||
225 | m_swapEventFilter = std::make_unique<SwapEventFilter>(window, glxWindow); | 202 | m_swapEventFilter = std::make_unique<SwapEventFilter>(window, glxWindow); | ||
226 | glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); | 203 | glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); | ||
227 | } | 204 | } | ||
228 | 205 | | |||
229 | haveSwapInterval = m_haveMESASwapControl || m_haveEXTSwapControl || m_haveSGISwapControl; | | |||
230 | | ||||
231 | setSupportsBufferAge(false); | 206 | setSupportsBufferAge(false); | ||
232 | | ||||
233 | if (hasExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) { | 207 | if (hasExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) { | ||
234 | const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); | 208 | const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); | ||
235 | 209 | if (useBufferAge != "0") { | |||
236 | if (useBufferAge != "0") | | |||
237 | setSupportsBufferAge(true); | 210 | setSupportsBufferAge(true); | ||
238 | } | 211 | } | ||
239 | | ||||
240 | setSyncsToVBlank(false); | | |||
241 | setBlocksForRetrace(false); | | |||
242 | haveWaitSync = false; | | |||
243 | gs_tripleBufferNeedsDetection = false; | | |||
244 | m_swapProfiler.init(); | | |||
245 | const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage; | | |||
246 | if (wantSync && glXIsDirect(display(), ctx)) { | | |||
247 | if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable | | |||
248 | setSwapInterval(1); | | |||
249 | setSyncsToVBlank(true); | | |||
250 | const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER"); | | |||
251 | if (!tripleBuffer.isEmpty()) { | | |||
252 | setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0); | | |||
253 | gs_tripleBufferUndetected = false; | | |||
254 | } | | |||
255 | gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected; | | |||
256 | } else if (hasExtension(QByteArrayLiteral("GLX_SGI_video_sync"))) { | | |||
257 | unsigned int sync; | | |||
258 | if (glXGetVideoSyncSGI(&sync) == 0 && glXWaitVideoSyncSGI(1, 0, &sync) == 0) { | | |||
259 | setSyncsToVBlank(true); | | |||
260 | setBlocksForRetrace(true); | | |||
261 | haveWaitSync = true; | | |||
262 | } else | | |||
263 | qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! glXSwapInterval is not supported, glXWaitVideoSync is supported but broken"; | | |||
264 | } else | | |||
265 | qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! neither glSwapInterval nor glXWaitVideoSync are supported"; | | |||
266 | } else { | | |||
267 | // disable v-sync (if possible) | | |||
268 | setSwapInterval(0); | | |||
269 | } | 212 | } | ||
213 | | ||||
270 | if (glPlatform->isVirtualBox()) { | 214 | if (glPlatform->isVirtualBox()) { | ||
271 | // VirtualBox does not support glxQueryDrawable | 215 | // VirtualBox does not support glxQueryDrawable | ||
272 | // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension | 216 | // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension | ||
273 | // and the GLPlatform has not been initialized at the moment when initGLX() is called. | 217 | // and the GLPlatform has not been initialized at the moment when initGLX() is called. | ||
274 | glXQueryDrawable = NULL; | 218 | glXQueryDrawable = NULL; | ||
275 | } | 219 | } | ||
276 | 220 | | |||
277 | setIsDirectRendering(bool(glXIsDirect(display(), ctx))); | 221 | setIsDirectRendering(bool(glXIsDirect(display(), ctx))); | ||
278 | 222 | | |||
279 | qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering(); | 223 | qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering(); | ||
280 | } | 224 | } | ||
281 | 225 | | |||
226 | bool GlxBackend::hasSwapEvent() const | ||||
227 | { | ||||
228 | return m_haveINTELSwapEvent; | ||||
229 | } | ||||
230 | | ||||
282 | bool GlxBackend::checkVersion() | 231 | bool GlxBackend::checkVersion() | ||
283 | { | 232 | { | ||
284 | int major, minor; | 233 | int major, minor; | ||
285 | glXQueryVersion(display(), &major, &minor); | 234 | glXQueryVersion(display(), &major, &minor); | ||
286 | return kVersionNumber(major, minor) >= kVersionNumber(1, 3); | 235 | return kVersionNumber(major, minor) >= kVersionNumber(1, 3); | ||
287 | } | 236 | } | ||
288 | 237 | | |||
289 | void GlxBackend::initExtensions() | 238 | void GlxBackend::initExtensions() | ||
▲ Show 20 Lines • Show All 164 Lines • ▼ Show 20 Line(s) | 400 | for (int i = 0; i < count; i++) { | |||
454 | glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); | 403 | glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); | ||
455 | 404 | | |||
456 | candidates.emplace_back(FBConfig{configs[i], depth, stencil}); | 405 | candidates.emplace_back(FBConfig{configs[i], depth, stencil}); | ||
457 | } | 406 | } | ||
458 | 407 | | |||
459 | if (count > 0) | 408 | if (count > 0) | ||
460 | XFree(configs); | 409 | XFree(configs); | ||
461 | 410 | | |||
462 | std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { | 411 | std::stable_sort(candidates.begin(), candidates.end(), | ||
412 | [](const FBConfig &left, const FBConfig &right) { | ||||
463 | if (left.depth < right.depth) | 413 | if (left.depth < right.depth) | ||
464 | return true; | 414 | return true; | ||
465 | 415 | | |||
466 | if (left.stencil < right.stencil) | 416 | if (left.stencil < right.stencil) | ||
467 | return true; | 417 | return true; | ||
468 | 418 | | |||
469 | return false; | 419 | return false; | ||
470 | }); | 420 | }); | ||
471 | 421 | | |||
472 | if (candidates.size() > 0) { | 422 | if (candidates.size() > 0) { | ||
473 | fbconfig = candidates.front().config; | 423 | fbconfig = candidates.front().config; | ||
474 | 424 | | |||
475 | int fbconfig_id, visual_id, red, green, blue, alpha, depth, stencil, srgb; | 425 | int fbconfig_id, visual_id, red, green, blue, alpha, depth, stencil, srgb; | ||
476 | glXGetFBConfigAttrib(display(), fbconfig, GLX_FBCONFIG_ID, &fbconfig_id); | 426 | glXGetFBConfigAttrib(display(), fbconfig, GLX_FBCONFIG_ID, &fbconfig_id); | ||
477 | glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, &visual_id); | 427 | glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, &visual_id); | ||
478 | glXGetFBConfigAttrib(display(), fbconfig, GLX_RED_SIZE, &red); | 428 | glXGetFBConfigAttrib(display(), fbconfig, GLX_RED_SIZE, &red); | ||
479 | glXGetFBConfigAttrib(display(), fbconfig, GLX_GREEN_SIZE, &green); | 429 | glXGetFBConfigAttrib(display(), fbconfig, GLX_GREEN_SIZE, &green); | ||
480 | glXGetFBConfigAttrib(display(), fbconfig, GLX_BLUE_SIZE, &blue); | 430 | glXGetFBConfigAttrib(display(), fbconfig, GLX_BLUE_SIZE, &blue); | ||
481 | glXGetFBConfigAttrib(display(), fbconfig, GLX_ALPHA_SIZE, &alpha); | 431 | glXGetFBConfigAttrib(display(), fbconfig, GLX_ALPHA_SIZE, &alpha); | ||
482 | glXGetFBConfigAttrib(display(), fbconfig, GLX_DEPTH_SIZE, &depth); | 432 | glXGetFBConfigAttrib(display(), fbconfig, GLX_DEPTH_SIZE, &depth); | ||
483 | glXGetFBConfigAttrib(display(), fbconfig, GLX_STENCIL_SIZE, &stencil); | 433 | glXGetFBConfigAttrib(display(), fbconfig, GLX_STENCIL_SIZE, &stencil); | ||
484 | glXGetFBConfigAttrib(display(), fbconfig, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &srgb); | 434 | glXGetFBConfigAttrib(display(), fbconfig, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &srgb); | ||
485 | 435 | | |||
486 | qCDebug(KWIN_X11STANDALONE, "Choosing GLXFBConfig %#x X visual %#x depth %d RGBA %d:%d:%d:%d ZS %d:%d sRGB: %d", | 436 | qCDebug(KWIN_X11STANDALONE, | ||
487 | fbconfig_id, visual_id, visualDepth(visual_id), red, green, blue, alpha, depth, stencil, srgb); | 437 | "Choosing GLXFBConfig %#x X visual %#x depth %d RGBA %d:%d:%d:%d ZS %d:%d sRGB: %d", | ||
438 | fbconfig_id, visual_id, visualDepth(visual_id), | ||||
439 | red, green, blue, alpha, depth, stencil, srgb); | ||||
488 | } | 440 | } | ||
489 | 441 | | |||
490 | if (fbconfig == nullptr) { | 442 | if (fbconfig == nullptr) { | ||
491 | qCCritical(KWIN_X11STANDALONE) << "Failed to find a usable framebuffer configuration"; | 443 | qCCritical(KWIN_X11STANDALONE) << "Failed to find a usable framebuffer configuration"; | ||
492 | return false; | 444 | return false; | ||
493 | } | 445 | } | ||
494 | 446 | | |||
495 | return true; | 447 | return true; | ||
▲ Show 20 Lines • Show All 171 Lines • ▼ Show 20 Line(s) | 614 | if (info->fbconfig) { | |||
667 | glXGetFBConfigAttrib(display(), info->fbconfig, GLX_VISUAL_ID, &visual_id); | 619 | glXGetFBConfigAttrib(display(), info->fbconfig, GLX_VISUAL_ID, &visual_id); | ||
668 | 620 | | |||
669 | qCDebug(KWIN_X11STANDALONE).nospace() << "Using FBConfig 0x" << hex << fbc_id << " for visual 0x" << hex << visual_id; | 621 | qCDebug(KWIN_X11STANDALONE).nospace() << "Using FBConfig 0x" << hex << fbc_id << " for visual 0x" << hex << visual_id; | ||
670 | } | 622 | } | ||
671 | 623 | | |||
672 | return info; | 624 | return info; | ||
673 | } | 625 | } | ||
674 | 626 | | |||
675 | void GlxBackend::setSwapInterval(int interval) | 627 | QRegion GlxBackend::prepareRenderingFrame() | ||
676 | { | 628 | { | ||
677 | if (m_haveEXTSwapControl) | 629 | const auto repaint = supportsBufferAge() ? accumulatedDamageHistory(m_bufferAge) : QRegion(); | ||
678 | glXSwapIntervalEXT(display(), glxWindow, interval); | | |||
679 | else if (m_haveMESASwapControl) | | |||
680 | glXSwapIntervalMESA(interval); | | |||
681 | else if (m_haveSGISwapControl) | | |||
682 | glXSwapIntervalSGI(interval); | | |||
683 | } | | |||
684 | | ||||
685 | void GlxBackend::waitSync() | | |||
686 | { | | |||
687 | // NOTE that vsync has no effect with indirect rendering | | |||
688 | if (haveWaitSync) { | | |||
689 | uint sync; | | |||
690 | #if 0 | | |||
691 | // TODO: why precisely is this important? | | |||
692 | // the sync counter /can/ perform multiple steps during glXGetVideoSync & glXWaitVideoSync | | |||
693 | // but this only leads to waiting for two frames??!? | | |||
694 | glXGetVideoSync(&sync); | | |||
695 | glXWaitVideoSync(2, (sync + 1) % 2, &sync); | | |||
696 | #else | | |||
697 | glXWaitVideoSyncSGI(1, 0, &sync); | | |||
698 | #endif | | |||
699 | } | | |||
700 | } | | |||
701 | 630 | | |||
702 | void GlxBackend::present() | 631 | startRenderTimer(); | ||
703 | { | 632 | // glXWaitX(); | ||
704 | if (lastDamage().isEmpty()) | | |||
705 | return; | | |||
706 | 633 | | |||
707 | const QSize &screenSize = screens()->size(); | 634 | return repaint; | ||
708 | const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); | 635 | } | ||
709 | const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion); | | |||
710 | 636 | | |||
711 | if (fullRepaint) { | 637 | void GlxBackend::swap() | ||
712 | if (m_haveINTELSwapEvent) | 638 | { | ||
639 | if (hasSwapEvent()) { | ||||
713 | Compositor::self()->aboutToSwapBuffers(); | 640 | Compositor::self()->aboutToSwapBuffers(); | ||
714 | | ||||
715 | if (haveSwapInterval) { | | |||
716 | if (gs_tripleBufferNeedsDetection) { | | |||
717 | glXWaitGL(); | | |||
718 | m_swapProfiler.begin(); | | |||
719 | } | 641 | } | ||
720 | glXSwapBuffers(display(), glxWindow); | 642 | glXSwapBuffers(display(), glxWindow); | ||
721 | if (gs_tripleBufferNeedsDetection) { | | |||
722 | glXWaitGL(); | | |||
723 | if (char result = m_swapProfiler.end()) { | | |||
724 | gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false; | | |||
725 | setBlocksForRetrace(result == 'd'); | | |||
726 | } | | |||
727 | } | | |||
728 | } else { | | |||
729 | waitSync(); | | |||
730 | glXSwapBuffers(display(), glxWindow); | | |||
731 | } | | |||
732 | if (supportsBufferAge()) { | 643 | if (supportsBufferAge()) { | ||
733 | glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); | 644 | glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); | ||
734 | } | 645 | } | ||
735 | } else if (m_haveMESACopySubBuffer) { | 646 | } | ||
647 | | ||||
648 | void GlxBackend::copy() | ||||
649 | { | ||||
650 | if (m_haveMESACopySubBuffer) { | ||||
651 | // Optimized copy is possible. | ||||
736 | for (const QRect &r : lastDamage()) { | 652 | for (const QRect &r : lastDamage()) { | ||
737 | // convert to OpenGL coordinates | 653 | // convert to OpenGL coordinates | ||
738 | int y = screenSize.height() - r.y() - r.height(); | 654 | int y = screens()->size().height() - r.y() - r.height(); | ||
739 | glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); | 655 | glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); | ||
740 | } | 656 | } | ||
741 | } else { // Copy Pixels (horribly slow on Mesa) | 657 | return; | ||
658 | } | ||||
659 | | ||||
660 | // Copy Pixels (horribly slow on Mesa) | ||||
742 | glDrawBuffer(GL_FRONT); | 661 | glDrawBuffer(GL_FRONT); | ||
743 | copyPixels(lastDamage()); | 662 | copyPixels(lastDamage()); | ||
744 | glDrawBuffer(GL_BACK); | 663 | glDrawBuffer(GL_BACK); | ||
745 | } | 664 | } | ||
746 | 665 | | |||
747 | setLastDamage(QRegion()); | 666 | void GlxBackend::present() | ||
748 | if (!supportsBufferAge()) { | | |||
749 | glXWaitGL(); | | |||
750 | XFlush(display()); | | |||
751 | } | | |||
752 | } | | |||
753 | | ||||
754 | void GlxBackend::screenGeometryChanged(const QSize &size) | | |||
755 | { | | |||
756 | doneCurrent(); | | |||
757 | | ||||
758 | XMoveResizeWindow(display(), window, 0, 0, size.width(), size.height()); | | |||
759 | overlayWindow()->setup(window); | | |||
760 | Xcb::sync(); | | |||
761 | | ||||
762 | makeCurrent(); | | |||
763 | glViewport(0, 0, size.width(), size.height()); | | |||
764 | | ||||
765 | // The back buffer contents are now undefined | | |||
766 | m_bufferAge = 0; | | |||
767 | } | | |||
768 | | ||||
769 | SceneOpenGLTexturePrivate *GlxBackend::createBackendTexture(SceneOpenGLTexture *texture) | | |||
770 | { | 667 | { | ||
771 | return new GlxTexture(texture, this); | 668 | Perf::ftrace(QStringLiteral("presentA")); | ||
669 | if (lastDamage().isEmpty()) { | ||||
670 | return; | ||||
772 | } | 671 | } | ||
773 | 672 | | |||
774 | QRegion GlxBackend::prepareRenderingFrame() | 673 | const QSize &screenSize = screens()->size(); | ||
775 | { | 674 | const bool isFullRepaint = (lastDamage() == | ||
776 | QRegion repaint; | 675 | QRegion(0, 0, screenSize.width(), screenSize.height())); | ||
777 | 676 | | |||
778 | if (gs_tripleBufferNeedsDetection) { | 677 | Perf::ftrace(QStringLiteral("presentB")); | ||
779 | // the composite timer floors the repaint frequency. This can pollute our triple buffering | 678 | if (supportsBufferAge() || isFullRepaint) { | ||
780 | // detection because the glXSwapBuffers call for the new frame has to wait until the pending | 679 | // Swap is possible because of full repaint or buffer age support. | ||
fredrik: Because the contents of the backbuffer are undefined after a buffer swap without… | |||||
Ok and in other case the back buffer also has what's currently on the front buffer and we can paint it partly over and then swap? Related to that do you know why we present() in prepareRenderingFrame (in DRM backend as well) and not after the actual paint in endRenderingFrame? romangg: Ok and in other case the back buffer also has what's currently on the front buffer and we can… | |||||
No, but the buffer age extension makes it possible to query the number of frames that have elapsed since the current back buffer was the front buffer. Based on that we can calculate how much we need to repaint to bring it up-to-date. See void OpenGLBackend::addToDamageHistory(const QRegion ®ion) and QRegion OpenGLBackend::accumulatedDamageHistory(int bufferAge) const
This code was written based on the idea that SwapBuffers() blocks until the swap is complete, which also means that it blocks until the next vblank. The logic can be illustrated with the following pseudo code: while (true) { SwapBuffers(); // For the previous frame renderTimer.start(); paint(); glFlush(); // Sleep until just before the next vblank int timeSinceLastVBlank = renderTimer.elapsed(); sleep(vblankInterval - timeSinceLastVBlank); } In practice we don't call sleep of course; instead we start the composite timer with the timeout set to just before the next vblank, and return to the event loop. But the only driver where SwapBuffers() behaves like this is the NVIDIA driver, and its not the default behavior. It's enabled by setting __GL_MaxFramesAllowed to 1 before initializing OpenGL. The reason the next frame is rendered immediately after the buffer swap instead of doing it as late as possible is to avoid missing the vblank when there is a sudden increase in render time. Also if we miss the vblank by one millisecond, the main thread will be blocked in SwapBuffers for 15 milliseconds, which is less than ideal. But there's obviously a trade-off here between smoothness and latency. fredrik: > Ok and in other case the back buffer also has what's currently on the front buffer and we can… | |||||
Thank you very much for the comprehensive explanation. I see the logic behind it now when the SwapBuffers call is assumed to be blocking. When it's not it seems just unnecessary complicated to me. Just do the SwapBuffers directly after paint() for the next frame at some point before the upcoming vblank. Then on buffer swap event or vblank interval timer timeout repeat for the next frame.
But only if the driver blocks on SwapBuffers and as you said no current driver behaves like this per default, right? Otherwise if we miss we "just" have a delay of one frame for the next paint result on screen (what we have with the current code on master all the time). romangg: > The logic can be illustrated with the following pseudo code: [...]
Thank you very much for… | |||||
Correction: in DRM backend we actually present on endRenderingFrame! romangg: > Related to that do you know why we present() in prepareRenderingFrame (in DRM backend as… | |||||
That's probably because the code in the GLX backend (and the timer code) was originally written for the NVIDIA driver, while the code in the DRM backend was not. But the GLX backend also presents in endRenderingFrame() when blocksForRetrace() returns false. fredrik: That's probably because the code in the GLX backend (and the timer code) was originally written… | |||||
781 | // one scanned out. | 680 | // TODO: Why is buffer age so important for that? We can also swap without it. | ||
782 | // So we compensate for that by waiting an extra milisecond to give the driver the chance to | 681 | swap(); | ||
783 | // fllush the buffer queue | 682 | } else { | ||
784 | usleep(1000); | 683 | copy(); | ||
785 | } | 684 | } | ||
786 | 685 | | |||
787 | present(); | 686 | setLastDamage(QRegion()); | ||
788 | | ||||
789 | if (supportsBufferAge()) | | |||
790 | repaint = accumulatedDamageHistory(m_bufferAge); | | |||
791 | | ||||
792 | startRenderTimer(); | | |||
793 | glXWaitX(); | | |||
794 | 687 | | |||
795 | return repaint; | 688 | // if (!supportsBufferAge()) { | ||
689 | // glXWaitGL(); | ||||
690 | // XFlush(display()); | ||||
691 | // } | ||||
796 | } | 692 | } | ||
797 | 693 | | |||
798 | void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) | 694 | void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) | ||
799 | { | 695 | { | ||
800 | if (damagedRegion.isEmpty()) { | 696 | if (damagedRegion.isEmpty()) { | ||
801 | setLastDamage(QRegion()); | 697 | setLastDamage(QRegion()); | ||
802 | 698 | | |||
803 | // If the damaged region of a window is fully occluded, the only | 699 | // If the damaged region of a window is fully occluded, the only | ||
804 | // rendering done, if any, will have been to repair a reused back | 700 | // rendering done, if any, will have been to repair a reused back | ||
805 | // buffer, making it identical to the front buffer. | 701 | // buffer, making it identical to the front buffer. | ||
806 | // | 702 | // | ||
807 | // In this case we won't post the back buffer. Instead we'll just | 703 | // In this case we won't post the back buffer. Instead we'll just | ||
808 | // set the buffer age to 1, so the repaired regions won't be | 704 | // set the buffer age to 1, so the repaired regions won't be | ||
809 | // rendered again in the next frame. | 705 | // rendered again in the next frame. | ||
810 | if (!renderedRegion.isEmpty()) | 706 | if (!renderedRegion.isEmpty()) | ||
811 | glFlush(); | 707 | glFlush(); | ||
812 | 708 | | |||
813 | m_bufferAge = 1; | 709 | m_bufferAge = 1; | ||
814 | return; | 710 | return; | ||
815 | } | 711 | } | ||
816 | 712 | | |||
817 | setLastDamage(renderedRegion); | 713 | setLastDamage(renderedRegion); | ||
818 | 714 | | |||
819 | if (!blocksForRetrace()) { | 715 | // Show the window only after the first pass, | ||
820 | // This also sets lastDamage to empty which prevents the frame from | 716 | // since that pass may take long. | ||
821 | // being posted again when prepareRenderingFrame() is called. | 717 | if (overlayWindow()->window()) | ||
822 | present(); | 718 | overlayWindow()->show(); | ||
823 | } else { | | |||
824 | // Make sure that the GPU begins processing the command stream | | |||
825 | // now and not the next time prepareRenderingFrame() is called. | | |||
826 | glFlush(); | | |||
827 | } | | |||
828 | | ||||
829 | if (overlayWindow()->window()) // show the window only after the first pass, | | |||
830 | overlayWindow()->show(); // since that pass may take long | | |||
831 | 719 | | |||
832 | // Save the damaged region to history | 720 | // Save the damaged region to history | ||
833 | if (supportsBufferAge()) | 721 | if (supportsBufferAge()) | ||
834 | addToDamageHistory(damagedRegion); | 722 | addToDamageHistory(damagedRegion); | ||
723 | | ||||
724 | present(); | ||||
725 | | ||||
726 | // qDebug() << "GlxBackend::endRenderingFrame END"; | ||||
727 | } | ||||
728 | | ||||
729 | void GlxBackend::screenGeometryChanged(const QSize &size) | ||||
730 | { | ||||
731 | doneCurrent(); | ||||
732 | | ||||
733 | XMoveResizeWindow(display(), window, 0, 0, size.width(), size.height()); | ||||
734 | overlayWindow()->setup(window); | ||||
735 | Xcb::sync(); | ||||
736 | | ||||
737 | makeCurrent(); | ||||
738 | glViewport(0, 0, size.width(), size.height()); | ||||
739 | | ||||
740 | // The back buffer contents are now undefined | ||||
741 | m_bufferAge = 0; | ||||
835 | } | 742 | } | ||
836 | 743 | | |||
837 | bool GlxBackend::makeCurrent() | 744 | bool GlxBackend::makeCurrent() | ||
838 | { | 745 | { | ||
839 | if (QOpenGLContext *context = QOpenGLContext::currentContext()) { | 746 | if (QOpenGLContext *context = QOpenGLContext::currentContext()) { | ||
840 | // Workaround to tell Qt that no QOpenGLContext is current | 747 | // Workaround to tell Qt that no QOpenGLContext is current | ||
841 | context->doneCurrent(); | 748 | context->doneCurrent(); | ||
842 | } | 749 | } | ||
Show All 11 Lines | 760 | { | |||
854 | return m_overlayWindow; | 761 | return m_overlayWindow; | ||
855 | } | 762 | } | ||
856 | 763 | | |||
857 | bool GlxBackend::usesOverlayWindow() const | 764 | bool GlxBackend::usesOverlayWindow() const | ||
858 | { | 765 | { | ||
859 | return true; | 766 | return true; | ||
860 | } | 767 | } | ||
861 | 768 | | |||
769 | SceneOpenGLTexturePrivate *GlxBackend::createBackendTexture(SceneOpenGLTexture *texture) | ||||
770 | { | ||||
771 | return new GlxTexture(texture, this); | ||||
772 | } | ||||
773 | | ||||
862 | /******************************************************** | 774 | /******************************************************** | ||
863 | * GlxTexture | 775 | * GlxTexture | ||
864 | *******************************************************/ | 776 | *******************************************************/ | ||
865 | GlxTexture::GlxTexture(SceneOpenGLTexture *texture, GlxBackend *backend) | 777 | GlxTexture::GlxTexture(SceneOpenGLTexture *texture, GlxBackend *backend) | ||
866 | : SceneOpenGLTexturePrivate() | 778 | : SceneOpenGLTexturePrivate() | ||
867 | , q(texture) | 779 | , q(texture) | ||
868 | , m_backend(backend) | 780 | , m_backend(backend) | ||
869 | , m_glxpixmap(None) | 781 | , m_glxpixmap(None) | ||
▲ Show 20 Lines • Show All 80 Lines • Show Last 20 Lines |
Because the contents of the backbuffer are undefined after a buffer swap without GLX_EXT_buffer_age.