Changeset View
Standalone View
plugins/platforms/x11/standalone/glxbackend.cpp
Show All 37 Lines | |||||
38 | // kwin libs | 38 | // kwin libs | ||
39 | #include <kwinglplatform.h> | 39 | #include <kwinglplatform.h> | ||
40 | #include <kwinglutils.h> | 40 | #include <kwinglutils.h> | ||
41 | #include <kwineffectquickview.h> | 41 | #include <kwineffectquickview.h> | ||
42 | #include <kwinxrenderutils.h> | 42 | #include <kwinxrenderutils.h> | ||
43 | // Qt | 43 | // Qt | ||
44 | #include <QDebug> | 44 | #include <QDebug> | ||
45 | #include <QOpenGLContext> | 45 | #include <QOpenGLContext> | ||
46 | #include <QThread> | ||||
46 | #include <QX11Info> | 47 | #include <QX11Info> | ||
47 | #include <QtPlatformHeaders/QGLXNativeContext> | 48 | #include <QtPlatformHeaders/QGLXNativeContext> | ||
48 | // system | 49 | // system | ||
49 | #include <unistd.h> | 50 | #include <unistd.h> | ||
50 | 51 | | |||
51 | #include <deque> | 52 | #include <deque> | ||
52 | #include <algorithm> | 53 | #include <algorithm> | ||
53 | #if HAVE_DL_LIBRARY | 54 | #if HAVE_DL_LIBRARY | ||
▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Line(s) | 89 | { | |||
100 | return false; | 101 | return false; | ||
101 | } | 102 | } | ||
102 | 103 | | |||
103 | 104 | | |||
104 | // ----------------------------------------------------------------------- | 105 | // ----------------------------------------------------------------------- | ||
105 | 106 | | |||
106 | 107 | | |||
107 | 108 | | |||
109 | AsyncSwapBuffersHandler::AsyncSwapBuffersHandler(Display *dpy, | ||||
110 | GLXFBConfig config, | ||||
111 | GLXDrawable drawable, | ||||
112 | GLXContext shareContext) | ||||
113 | : QObject(), | ||||
114 | m_dpy(dpy), | ||||
115 | m_drawable(drawable) | ||||
116 | { | ||||
117 | // Create the context we will make current to the other thread | ||||
118 | const int attribs[] = { | ||||
119 | GLX_CONTEXT_MAJOR_VERSION_ARB, 2, | ||||
120 | GLX_CONTEXT_MINOR_VERSION_ARB, 1, | ||||
121 | 0 | ||||
davidedmundson: One thing I found with nvidia drivers is that if we create an original context with robustness… | |||||
Ok, let's remember this information. We can first test on master without it though. romangg: Ok, let's remember this information. We can first test on master without it though. | |||||
It's not going to work properly until that has been fixed, so we certainly can't push it to master before then. I think the easiest way to fix it is to save the attribute builder or the attribute list that was used to create the main context, and reuse it. fredrik: It's not going to work properly until that has been fixed, so we certainly can't push it to… | |||||
122 | }; | ||||
123 | | ||||
124 | m_context = glXCreateContextAttribsARB(m_dpy, config, shareContext, True /*direct*/, attribs); | ||||
125 | } | ||||
126 | | ||||
127 | AsyncSwapBuffersHandler::~AsyncSwapBuffersHandler() | ||||
128 | { | ||||
129 | glXDestroyContext(m_dpy, m_context); | ||||
130 | } | ||||
131 | | ||||
132 | void AsyncSwapBuffersHandler::makeCurrent() | ||||
133 | { | ||||
134 | glXMakeContextCurrent(m_dpy, m_drawable, m_drawable, m_context); | ||||
135 | } | ||||
136 | | ||||
137 | void AsyncSwapBuffersHandler::swapBuffers(GLsync fence) | ||||
138 | { | ||||
139 | const GLenum result = glClientWaitSync(fence, 0, GL_TIMEOUT_IGNORED); | ||||
140 | if (result != GL_ALREADY_SIGNALED && result != GL_CONDITION_SATISFIED) { | ||||
141 | qCWarning(KWIN_X11STANDALONE) << "glClientWaitSync() before buffer swap failed!"; | ||||
142 | } | ||||
143 | | ||||
144 | glXSwapBuffers(m_dpy, m_drawable); | ||||
145 | | ||||
146 | const std::chrono::nanoseconds now = std::chrono::steady_clock::now().time_since_epoch(); | ||||
147 | const std::chrono::microseconds timestamp = | ||||
148 | std::chrono::duration_cast<std::chrono::microseconds>(now); | ||||
149 | | ||||
150 | emit swapComplete(timestamp); | ||||
151 | | ||||
152 | glDeleteSync(fence); | ||||
153 | } | ||||
154 | | ||||
155 | | ||||
156 | // ----------------------------------------------------------------------- | ||||
157 | | ||||
158 | | ||||
159 | | ||||
108 | GlxBackend::GlxBackend(Display *display) | 160 | GlxBackend::GlxBackend(Display *display) | ||
109 | : OpenGLBackend() | 161 | : OpenGLBackend() | ||
110 | , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) | 162 | , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) | ||
111 | , window(None) | 163 | , window(None) | ||
112 | , fbconfig(nullptr) | 164 | , fbconfig(nullptr) | ||
113 | , glxWindow(None) | 165 | , glxWindow(None) | ||
114 | , ctx(nullptr) | 166 | , ctx(nullptr) | ||
115 | , m_bufferAge(0) | 167 | , m_bufferAge(0) | ||
116 | , m_x11Display(display) | 168 | , m_x11Display(display) | ||
117 | { | 169 | { | ||
170 | // Ensures calls to glXSwapBuffers will always block until the next | ||||
171 | // retrace when using the proprietary NVIDIA driver. This must be | ||||
172 | // set before libGL.so is loaded. | ||||
173 | setenv("__GL_MaxFramesAllowed", "1", true); | ||||
174 | | ||||
118 | // Force initialization of GLX integration in the Qt's xcb backend | 175 | // Force initialization of GLX integration in the Qt's xcb backend | ||
119 | // to make it call XESetWireToEvent callbacks, which is required | 176 | // to make it call XESetWireToEvent callbacks, which is required | ||
120 | // by Mesa when using DRI2. | 177 | // by Mesa when using DRI2. | ||
121 | QOpenGLContext::supportsThreadedOpenGL(); | 178 | QOpenGLContext::supportsThreadedOpenGL(); | ||
122 | } | 179 | } | ||
123 | 180 | | |||
124 | GlxBackend::~GlxBackend() | 181 | GlxBackend::~GlxBackend() | ||
125 | { | 182 | { | ||
183 | m_asyncSwapThread->quit(); | ||||
184 | | ||||
126 | if (isFailed()) { | 185 | if (isFailed()) { | ||
127 | m_overlayWindow->destroy(); | 186 | m_overlayWindow->destroy(); | ||
128 | } | 187 | } | ||
129 | // TODO: cleanup in error case | 188 | // TODO: cleanup in error case | ||
130 | // do cleanup after initBuffer() | 189 | // do cleanup after initBuffer() | ||
131 | cleanupGL(); | 190 | cleanupGL(); | ||
132 | doneCurrent(); | 191 | doneCurrent(); | ||
133 | EffectQuickView::setShareContext(nullptr); | 192 | EffectQuickView::setShareContext(nullptr); | ||
134 | 193 | | |||
135 | if (ctx) | 194 | if (ctx) | ||
136 | glXDestroyContext(display(), ctx); | 195 | glXDestroyContext(display(), ctx); | ||
137 | 196 | | |||
138 | if (glxWindow) | 197 | if (glxWindow) | ||
139 | glXDestroyWindow(display(), glxWindow); | 198 | glXDestroyWindow(display(), glxWindow); | ||
140 | 199 | | |||
141 | if (window) | 200 | if (window) | ||
142 | XDestroyWindow(display(), window); | 201 | XDestroyWindow(display(), window); | ||
143 | 202 | | |||
144 | qDeleteAll(m_fbconfigHash); | 203 | qDeleteAll(m_fbconfigHash); | ||
145 | m_fbconfigHash.clear(); | 204 | m_fbconfigHash.clear(); | ||
146 | 205 | | |||
147 | overlayWindow()->destroy(); | 206 | overlayWindow()->destroy(); | ||
148 | delete m_overlayWindow; | 207 | delete m_overlayWindow; | ||
208 | | ||||
209 | m_asyncSwapThread->wait(); | ||||
210 | delete m_asyncSwapHandler; | ||||
211 | delete m_asyncSwapThread; | ||||
149 | } | 212 | } | ||
150 | 213 | | |||
151 | typedef void (*glXFuncPtr)(); | 214 | typedef void (*glXFuncPtr)(); | ||
152 | 215 | | |||
153 | static glXFuncPtr getProcAddress(const char* name) | 216 | static glXFuncPtr getProcAddress(const char* name) | ||
154 | { | 217 | { | ||
155 | glXFuncPtr ret = nullptr; | 218 | glXFuncPtr ret = nullptr; | ||
156 | #if HAVE_EPOXY_GLX | 219 | #if HAVE_EPOXY_GLX | ||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Line(s) | 231 | { | |||
208 | m_haveEXTSwapControl = hasExtension(QByteArrayLiteral("GLX_EXT_swap_control")); | 271 | m_haveEXTSwapControl = hasExtension(QByteArrayLiteral("GLX_EXT_swap_control")); | ||
209 | 272 | | |||
210 | // Allow to disable Intel swap event with env variable. There were problems in the past. | 273 | // Allow to disable Intel swap event with env variable. There were problems in the past. | ||
211 | // See BUG 342582. | 274 | // See BUG 342582. | ||
212 | if (hasExtension(QByteArrayLiteral("GLX_INTEL_swap_event")) && | 275 | if (hasExtension(QByteArrayLiteral("GLX_INTEL_swap_event")) && | ||
213 | qgetenv("KWIN_USE_INTEL_SWAP_EVENT") != QByteArrayLiteral("0")) { | 276 | qgetenv("KWIN_USE_INTEL_SWAP_EVENT") != QByteArrayLiteral("0")) { | ||
214 | m_swapEventFilter = std::make_unique<SwapEventFilter>(window, glxWindow); | 277 | m_swapEventFilter = std::make_unique<SwapEventFilter>(window, glxWindow); | ||
215 | glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); | 278 | glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); | ||
279 | } else { | ||||
280 | m_asyncSwapHandler = new AsyncSwapBuffersHandler(display(), fbconfig, glxWindow, ctx); | ||||
281 | QObject::connect(m_asyncSwapHandler, | ||||
282 | &AsyncSwapBuffersHandler::swapComplete, | ||||
283 | [this](std::chrono::microseconds timestamp) { | ||||
284 | Q_UNUSED(timestamp) | ||||
285 | | ||||
286 | if (supportsBufferAge()) { | ||||
287 | glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, | ||||
288 | (GLuint *) &m_bufferAge); | ||||
289 | } | ||||
290 | | ||||
291 | Compositor::self()->bufferSwapComplete(); | ||||
292 | }); | ||||
293 | | ||||
294 | m_asyncSwapThread = new QThread; | ||||
295 | m_asyncSwapHandler->moveToThread(m_asyncSwapThread); | ||||
296 | m_asyncSwapThread->start(); | ||||
297 | | ||||
298 | QMetaObject::invokeMethod(m_asyncSwapHandler, | ||||
299 | &AsyncSwapBuffersHandler::makeCurrent, | ||||
300 | Qt::QueuedConnection); | ||||
216 | } | 301 | } | ||
217 | 302 | | |||
218 | setSupportsBufferAge(false); | 303 | setSupportsBufferAge(false); | ||
219 | 304 | | |||
220 | if (hasExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) { | 305 | if (hasExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) { | ||
221 | const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); | 306 | const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); | ||
222 | 307 | | |||
223 | if (useBufferAge != "0") | 308 | if (useBufferAge != "0") | ||
Show All 10 Lines | |||||
234 | 319 | | |||
235 | if (glPlatform->isVirtualBox()) { | 320 | if (glPlatform->isVirtualBox()) { | ||
236 | // VirtualBox does not support glxQueryDrawable | 321 | // VirtualBox does not support glxQueryDrawable | ||
237 | // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension | 322 | // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension | ||
238 | // and the GLPlatform has not been initialized at the moment when initGLX() is called. | 323 | // and the GLPlatform has not been initialized at the moment when initGLX() is called. | ||
239 | glXQueryDrawable = nullptr; | 324 | glXQueryDrawable = nullptr; | ||
240 | } | 325 | } | ||
241 | 326 | | |||
242 | setIsDirectRendering(bool(glXIsDirect(display(), ctx))); | 327 | setIsDirectRendering(bool(glXIsDirect(display(), ctx))); | ||
Can there be a case where we don't have intel-swap-event but also don't want the swap-handler to be activated? Some legacy systems for example. I remember you provided the table with which hardware uses what extension so you know I'm sure. romangg: Can there be a case where we don't have intel-swap-event but also don't want the swap-handler… | |||||
The swap-handler should work with other implementations (famous last words?), but we don't know if SwapBuffers() is going to block. If it doesn't, we can't rely on the swap event for scheduling. So if we want to play it safe, we should only enable it on NVIDIA. fredrik: The swap-handler should work with other implementations (famous last words?), but we don't know… | |||||
243 | 328 | | |||
244 | qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering(); | 329 | qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering(); | ||
245 | } | 330 | } | ||
246 | 331 | | |||
247 | bool GlxBackend::checkVersion() | 332 | bool GlxBackend::checkVersion() | ||
248 | { | 333 | { | ||
249 | int major, minor; | 334 | int major, minor; | ||
250 | glXQueryVersion(display(), &major, &minor); | 335 | glXQueryVersion(display(), &major, &minor); | ||
251 | return kVersionNumber(major, minor) >= kVersionNumber(1, 3); | 336 | return kVersionNumber(major, minor) >= kVersionNumber(1, 3); | ||
252 | } | 337 | } | ||
253 | 338 | | |||
254 | void GlxBackend::initExtensions() | 339 | void GlxBackend::initExtensions() | ||
255 | { | 340 | { | ||
256 | const QByteArray string = (const char *) glXQueryExtensionsString(display(), QX11Info::appScreen()); | 341 | const QByteArray string = (const char *) glXQueryExtensionsString(display(), QX11Info::appScreen()); | ||
257 | setExtensions(string.split(' ')); | 342 | setExtensions(string.split(' ')); | ||
258 | } | 343 | } | ||
Is this necessary? If the swap thread isn't rendering anything I don't think it actually needs a current context, right? ekurzinger: Is this necessary? If the swap thread isn't rendering anything I don't think it actually needs… | |||||
Yeah, I actually just assumed that glXSwapBuffers() needs a current context, but indeed the specification doesn't say that anywhere. fredrik: Yeah, I actually just assumed that glXSwapBuffers() needs a current context, but indeed the… | |||||
259 | 344 | | |||
260 | bool GlxBackend::initRenderingContext() | 345 | bool GlxBackend::initRenderingContext() | ||
261 | { | 346 | { | ||
262 | const bool direct = true; | 347 | const bool direct = true; | ||
263 | 348 | | |||
264 | // Use glXCreateContextAttribsARB() when it's available | 349 | // Use glXCreateContextAttribsARB() when it's available | ||
265 | if (hasExtension(QByteArrayLiteral("GLX_ARB_create_context"))) { | 350 | if (hasExtension(QByteArrayLiteral("GLX_ARB_create_context"))) { | ||
266 | const bool have_robustness = hasExtension(QByteArrayLiteral("GLX_ARB_create_context_robustness")); | 351 | const bool have_robustness = hasExtension(QByteArrayLiteral("GLX_ARB_create_context_robustness")); | ||
▲ Show 20 Lines • Show All 400 Lines • ▼ Show 20 Line(s) | 747 | { | |||
667 | const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); | 752 | const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); | ||
668 | const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion); | 753 | const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion); | ||
669 | 754 | | |||
670 | if (fullRepaint) { | 755 | if (fullRepaint) { | ||
671 | if (hasSwapEvent()) { | 756 | if (hasSwapEvent()) { | ||
672 | Compositor::self()->aboutToSwapBuffers(); | 757 | Compositor::self()->aboutToSwapBuffers(); | ||
673 | } | 758 | } | ||
674 | 759 | | |||
760 | if (m_asyncSwapHandler) { | ||||
761 | GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | ||||
I'm not 100% sure this is sufficient to ensure all rendering is complete before the swap. It only guarantees that any commands have been submitted to the GPU, but not necessarily that they've all finished executing. Furthermore, glXSwapBuffers should cause an implicit glFlush anyway. For instance, the glXSwapBuffers spec mentions glFinish for this purpose:
ekurzinger: I'm not 100% sure this is sufficient to ensure all rendering is complete before the swap. It… | |||||
Yeah, I was actually thinking that we may need to pass a fence to the other thread to ensure that. glXSwapBuffers() only flushes the context current to the thread where it is called, so that's why I added the explicit flush. fredrik: Yeah, I was actually thinking that we may need to pass a fence to the other thread to ensure… | |||||
762 | glFlush(); | ||||
763 | QMetaObject::invokeMethod(m_asyncSwapHandler, | ||||
764 | [=]{ m_asyncSwapHandler->swapBuffers(fence); }, | ||||
765 | Qt::QueuedConnection); | ||||
766 | } else { | ||||
675 | glXSwapBuffers(display(), glxWindow); | 767 | glXSwapBuffers(display(), glxWindow); | ||
676 | 768 | | |||
677 | if (supportsBufferAge()) { | 769 | if (supportsBufferAge()) { | ||
678 | glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); | 770 | glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, | ||
771 | (GLuint *) &m_bufferAge); | ||||
772 | } | ||||
679 | } | 773 | } | ||
680 | } else if (m_haveMESACopySubBuffer) { | 774 | } else if (m_haveMESACopySubBuffer) { | ||
681 | for (const QRect &r : lastDamage()) { | 775 | for (const QRect &r : lastDamage()) { | ||
682 | // convert to OpenGL coordinates | 776 | // convert to OpenGL coordinates | ||
683 | int y = screenSize.height() - r.y() - r.height(); | 777 | int y = screenSize.height() - r.y() - r.height(); | ||
684 | glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); | 778 | glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); | ||
685 | } | 779 | } | ||
686 | } else { // Copy Pixels (horribly slow on Mesa) | 780 | } else { // Copy Pixels (horribly slow on Mesa) | ||
▲ Show 20 Lines • Show All 93 Lines • ▼ Show 20 Line(s) | |||||
780 | 874 | | |||
781 | bool GlxBackend::usesOverlayWindow() const | 875 | bool GlxBackend::usesOverlayWindow() const | ||
782 | { | 876 | { | ||
783 | return true; | 877 | return true; | ||
784 | } | 878 | } | ||
785 | 879 | | |||
786 | bool GlxBackend::hasSwapEvent() const | 880 | bool GlxBackend::hasSwapEvent() const | ||
787 | { | 881 | { | ||
788 | return m_swapEventFilter != nullptr; | 882 | return m_swapEventFilter != nullptr || m_asyncSwapHandler != nullptr; | ||
789 | } | 883 | } | ||
790 | 884 | | |||
791 | /******************************************************** | 885 | /******************************************************** | ||
792 | * GlxTexture | 886 | * GlxTexture | ||
793 | *******************************************************/ | 887 | *******************************************************/ | ||
794 | GlxTexture::GlxTexture(SceneOpenGLTexture *texture, GlxBackend *backend) | 888 | GlxTexture::GlxTexture(SceneOpenGLTexture *texture, GlxBackend *backend) | ||
795 | : SceneOpenGLTexturePrivate() | 889 | : SceneOpenGLTexturePrivate() | ||
796 | , q(texture) | 890 | , q(texture) | ||
▲ Show 20 Lines • Show All 82 Lines • Show Last 20 Lines |
One thing I found with nvidia drivers is that if we create an original context with robustness attributes enabled, and then use that as a share context for a new context without robustness, it fails to create anything.
It looks like this code would do that.
We probably want to re-use the kwin code for creating contexts so we can ensure matching attributes