1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the plugins of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qeglfscursor_p.h" |
41 | #include "qeglfsintegration_p.h" |
42 | #include "qeglfsscreen_p.h" |
43 | #include "qeglfscontext_p.h" |
44 | |
45 | #include <qpa/qwindowsysteminterface.h> |
46 | #include <QtGui/QOpenGLContext> |
47 | #include <QtCore/QFile> |
48 | #include <QtCore/QJsonDocument> |
49 | #include <QtCore/QJsonArray> |
50 | #include <QtCore/QJsonObject> |
51 | |
52 | #include <QtGui/private/qguiapplication_p.h> |
53 | #include <QtOpenGL/private/qopenglvertexarrayobject_p.h> |
54 | |
55 | #ifndef GL_VERTEX_ARRAY_BINDING |
56 | #define GL_VERTEX_ARRAY_BINDING 0x85B5 |
57 | #endif |
58 | |
59 | QT_BEGIN_NAMESPACE |
60 | |
61 | QEglFSCursor::QEglFSCursor(QPlatformScreen *screen) |
62 | : m_visible(true), |
63 | m_screen(static_cast<QEglFSScreen *>(screen)), |
64 | m_activeScreen(nullptr), |
65 | m_deviceListener(nullptr), |
66 | m_updateRequested(false) |
67 | { |
68 | QByteArray hideCursorVal = qgetenv("QT_QPA_EGLFS_HIDECURSOR" ); |
69 | if (!hideCursorVal.isEmpty()) |
70 | m_visible = hideCursorVal.toInt() == 0; |
71 | if (!m_visible) |
72 | return; |
73 | |
74 | int rotation = qEnvironmentVariableIntValue("QT_QPA_EGLFS_ROTATION" ); |
75 | if (rotation) |
76 | m_rotationMatrix.rotate(rotation, 0, 0, 1); |
77 | |
78 | // Try to load the cursor atlas. If this fails, m_visible is set to false and |
79 | // paintOnScreen() and setCurrentCursor() become no-ops. |
80 | initCursorAtlas(); |
81 | |
82 | // initialize the cursor |
83 | #ifndef QT_NO_CURSOR |
84 | QCursor cursor(Qt::ArrowCursor); |
85 | setCurrentCursor(&cursor); |
86 | #endif |
87 | |
88 | m_deviceListener = new QEglFSCursorDeviceListener(this); |
89 | connect(QGuiApplicationPrivate::inputDeviceManager(), &QInputDeviceManager::deviceListChanged, |
90 | m_deviceListener, &QEglFSCursorDeviceListener::onDeviceListChanged); |
91 | updateMouseStatus(); |
92 | } |
93 | |
94 | QEglFSCursor::~QEglFSCursor() |
95 | { |
96 | resetResources(); |
97 | delete m_deviceListener; |
98 | } |
99 | |
100 | void QEglFSCursor::updateMouseStatus() |
101 | { |
102 | m_visible = m_deviceListener->hasMouse(); |
103 | } |
104 | |
105 | bool QEglFSCursorDeviceListener::hasMouse() const |
106 | { |
107 | return QGuiApplicationPrivate::inputDeviceManager()->deviceCount(QInputDeviceManager::DeviceTypePointer) > 0; |
108 | } |
109 | |
110 | void QEglFSCursorDeviceListener::onDeviceListChanged(QInputDeviceManager::DeviceType type) |
111 | { |
112 | if (type == QInputDeviceManager::DeviceTypePointer) |
113 | m_cursor->updateMouseStatus(); |
114 | } |
115 | |
116 | void QEglFSCursor::resetResources() |
117 | { |
118 | m_cursor.customCursorPending = !m_cursor.customCursorImage.isNull(); |
119 | } |
120 | |
121 | void QEglFSCursor::createShaderPrograms() |
122 | { |
123 | static const char *textureVertexProgram = |
124 | "attribute highp vec2 vertexCoordEntry;\n" |
125 | "attribute highp vec2 textureCoordEntry;\n" |
126 | "varying highp vec2 textureCoord;\n" |
127 | "uniform highp mat4 mat;\n" |
128 | "void main() {\n" |
129 | " textureCoord = textureCoordEntry;\n" |
130 | " gl_Position = mat * vec4(vertexCoordEntry, 1.0, 1.0);\n" |
131 | "}\n" ; |
132 | |
133 | static const char *textureFragmentProgram = |
134 | "uniform sampler2D texture;\n" |
135 | "varying highp vec2 textureCoord;\n" |
136 | "void main() {\n" |
137 | " gl_FragColor = texture2D(texture, textureCoord).bgra;\n" |
138 | "}\n" ; |
139 | |
140 | QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData; |
141 | gfx.program.reset(new QOpenGLShaderProgram); |
142 | gfx.program->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, textureVertexProgram); |
143 | gfx.program->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, textureFragmentProgram); |
144 | gfx.program->bindAttributeLocation("vertexCoordEntry" , 0); |
145 | gfx.program->bindAttributeLocation("textureCoordEntry" , 1); |
146 | gfx.program->link(); |
147 | |
148 | gfx.textureEntry = gfx.program->uniformLocation("texture" ); |
149 | gfx.matEntry = gfx.program->uniformLocation("mat" ); |
150 | } |
151 | |
152 | void QEglFSCursor::createCursorTexture(uint *texture, const QImage &image) |
153 | { |
154 | if (!*texture) |
155 | glGenTextures(1, texture); |
156 | glBindTexture(GL_TEXTURE_2D, *texture); |
157 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
158 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
159 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
160 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
161 | |
162 | glTexImage2D(GL_TEXTURE_2D, 0 /* level */, GL_RGBA, image.width(), image.height(), 0 /* border */, |
163 | GL_RGBA, GL_UNSIGNED_BYTE, image.constBits()); |
164 | } |
165 | |
166 | void QEglFSCursor::initCursorAtlas() |
167 | { |
168 | static QByteArray json = qgetenv("QT_QPA_EGLFS_CURSOR" ); |
169 | if (json.isEmpty()) |
170 | json = ":/cursor.json" ; |
171 | |
172 | QFile file(QString::fromUtf8(json)); |
173 | if (!file.open(QFile::ReadOnly)) { |
174 | m_visible = false; |
175 | return; |
176 | } |
177 | |
178 | QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); |
179 | QJsonObject object = doc.object(); |
180 | |
181 | QString atlas = object.value(QLatin1String("image" )).toString(); |
182 | Q_ASSERT(!atlas.isEmpty()); |
183 | |
184 | const int cursorsPerRow = object.value(QLatin1String("cursorsPerRow" )).toDouble(); |
185 | Q_ASSERT(cursorsPerRow); |
186 | m_cursorAtlas.cursorsPerRow = cursorsPerRow; |
187 | |
188 | const QJsonArray hotSpots = object.value(QLatin1String("hotSpots" )).toArray(); |
189 | Q_ASSERT(hotSpots.count() == Qt::LastCursor + 1); |
190 | for (int i = 0; i < hotSpots.count(); i++) { |
191 | QPoint hotSpot(hotSpots[i].toArray()[0].toDouble(), hotSpots[i].toArray()[1].toDouble()); |
192 | m_cursorAtlas.hotSpots << hotSpot; |
193 | } |
194 | |
195 | QImage image = QImage(atlas).convertToFormat(QImage::Format_ARGB32_Premultiplied); |
196 | m_cursorAtlas.cursorWidth = image.width() / m_cursorAtlas.cursorsPerRow; |
197 | m_cursorAtlas.cursorHeight = image.height() / ((Qt::LastCursor + cursorsPerRow) / cursorsPerRow); |
198 | m_cursorAtlas.width = image.width(); |
199 | m_cursorAtlas.height = image.height(); |
200 | m_cursorAtlas.image = image; |
201 | } |
202 | |
203 | #ifndef QT_NO_CURSOR |
204 | void QEglFSCursor::changeCursor(QCursor *cursor, QWindow *window) |
205 | { |
206 | Q_UNUSED(window); |
207 | const QRect oldCursorRect = cursorRect(); |
208 | if (setCurrentCursor(cursor)) |
209 | update(oldCursorRect | cursorRect(), false); |
210 | } |
211 | |
212 | bool QEglFSCursor::setCurrentCursor(QCursor *cursor) |
213 | { |
214 | if (!m_visible) |
215 | return false; |
216 | |
217 | const Qt::CursorShape newShape = cursor ? cursor->shape() : Qt::ArrowCursor; |
218 | if (m_cursor.shape == newShape && newShape != Qt::BitmapCursor) |
219 | return false; |
220 | |
221 | if (m_cursor.shape == Qt::BitmapCursor) { |
222 | m_cursor.customCursorImage = QImage(); |
223 | m_cursor.customCursorPending = false; |
224 | } |
225 | m_cursor.shape = newShape; |
226 | if (newShape != Qt::BitmapCursor) { // standard cursor |
227 | const float ws = (float)m_cursorAtlas.cursorWidth / m_cursorAtlas.width, |
228 | hs = (float)m_cursorAtlas.cursorHeight / m_cursorAtlas.height; |
229 | m_cursor.textureRect = QRectF(ws * (m_cursor.shape % m_cursorAtlas.cursorsPerRow), |
230 | hs * (m_cursor.shape / m_cursorAtlas.cursorsPerRow), |
231 | ws, hs); |
232 | m_cursor.hotSpot = m_cursorAtlas.hotSpots[m_cursor.shape]; |
233 | m_cursor.useCustomCursor = false; |
234 | m_cursor.size = QSize(m_cursorAtlas.cursorWidth, m_cursorAtlas.cursorHeight); |
235 | } else { |
236 | QImage image = cursor->pixmap().toImage(); |
237 | m_cursor.textureRect = QRectF(0, 0, 1, 1); |
238 | m_cursor.hotSpot = cursor->hotSpot(); |
239 | m_cursor.useCustomCursor = false; // will get updated in the next render() |
240 | m_cursor.size = image.size(); |
241 | m_cursor.customCursorImage = image; |
242 | m_cursor.customCursorPending = true; |
243 | m_cursor.customCursorKey = m_cursor.customCursorImage.cacheKey(); |
244 | } |
245 | |
246 | return true; |
247 | } |
248 | #endif |
249 | |
250 | class CursorUpdateEvent : public QEvent |
251 | { |
252 | public: |
253 | CursorUpdateEvent(const QPoint &pos, const QRect &rect, bool allScreens) |
254 | : QEvent(QEvent::Type(QEvent::User + 1)), |
255 | m_pos(pos), |
256 | m_rect(rect), |
257 | m_allScreens(allScreens) |
258 | { } |
259 | QPoint pos() const { return m_pos; } |
260 | QRegion rect() const { return m_rect; } |
261 | bool allScreens() const { return m_allScreens; } |
262 | |
263 | private: |
264 | QPoint m_pos; |
265 | QRect m_rect; |
266 | bool m_allScreens; |
267 | }; |
268 | |
269 | bool QEglFSCursor::event(QEvent *e) |
270 | { |
271 | if (e->type() == QEvent::User + 1) { |
272 | CursorUpdateEvent *ev = static_cast<CursorUpdateEvent *>(e); |
273 | m_updateRequested = false; |
274 | if (!ev->allScreens()) { |
275 | QWindow *w = m_screen->topLevelAt(ev->pos()); // works for the entire virtual desktop, no need to loop |
276 | if (w) { |
277 | QWindowSystemInterface::handleExposeEvent(w, ev->rect()); |
278 | QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); |
279 | } |
280 | } else { |
281 | for (QWindow *w : qGuiApp->topLevelWindows()) |
282 | QWindowSystemInterface::handleExposeEvent(w, w->geometry()); |
283 | QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); |
284 | } |
285 | return true; |
286 | } |
287 | return QPlatformCursor::event(e); |
288 | } |
289 | |
290 | void QEglFSCursor::update(const QRect &rect, bool allScreens) |
291 | { |
292 | if (!m_updateRequested) { |
293 | // Must not flush the window system events directly from here since we are likely to |
294 | // be a called directly from QGuiApplication's processMouseEvents. Flushing events |
295 | // could cause reentering by dispatching more queued mouse events. |
296 | m_updateRequested = true; |
297 | QCoreApplication::postEvent(this, new CursorUpdateEvent(m_cursor.pos, rect, allScreens)); |
298 | } |
299 | } |
300 | |
301 | QRect QEglFSCursor::cursorRect() const |
302 | { |
303 | return QRect(m_cursor.pos - m_cursor.hotSpot, m_cursor.size); |
304 | } |
305 | |
306 | QPoint QEglFSCursor::pos() const |
307 | { |
308 | return m_cursor.pos; |
309 | } |
310 | |
311 | void QEglFSCursor::setPos(const QPoint &pos) |
312 | { |
313 | QGuiApplicationPrivate::inputDeviceManager()->setCursorPos(pos); |
314 | const QRect oldCursorRect = cursorRect(); |
315 | m_cursor.pos = pos; |
316 | update(oldCursorRect | cursorRect(), false); |
317 | for (QPlatformScreen *screen : m_screen->virtualSiblings()) |
318 | static_cast<QEglFSScreen *>(screen)->handleCursorMove(m_cursor.pos); |
319 | } |
320 | |
321 | void QEglFSCursor::pointerEvent(const QMouseEvent &event) |
322 | { |
323 | if (event.type() != QEvent::MouseMove) |
324 | return; |
325 | const QRect oldCursorRect = cursorRect(); |
326 | m_cursor.pos = event.globalPosition().toPoint(); |
327 | update(oldCursorRect | cursorRect(), false); |
328 | for (QPlatformScreen *screen : m_screen->virtualSiblings()) |
329 | static_cast<QEglFSScreen *>(screen)->handleCursorMove(m_cursor.pos); |
330 | } |
331 | |
332 | void QEglFSCursor::paintOnScreen() |
333 | { |
334 | if (!m_visible) |
335 | return; |
336 | |
337 | // cr must be a QRectF, otherwise cr.right() and bottom() would be off by |
338 | // one in the calculations below. |
339 | QRectF cr = cursorRect(); // hotspot included |
340 | |
341 | // Support virtual desktop too. Backends with multi-screen support (e.g. all |
342 | // variants of KMS/DRM) will enable this by default. In this case all |
343 | // screens are siblings of each other. When not enabled, the sibling list |
344 | // only contains m_screen itself. |
345 | for (QPlatformScreen *screen : m_screen->virtualSiblings()) { |
346 | if (screen->geometry().contains(cr.topLeft().toPoint() + m_cursor.hotSpot) |
347 | && QOpenGLContext::currentContext()->screen() == screen->screen()) |
348 | { |
349 | cr.translate(-screen->geometry().topLeft()); |
350 | const QSize screenSize = screen->geometry().size(); |
351 | const GLfloat x1 = 2 * (cr.left() / GLfloat(screenSize.width())) - 1; |
352 | const GLfloat x2 = 2 * (cr.right() / GLfloat(screenSize.width())) - 1; |
353 | const GLfloat y1 = 1 - (cr.top() / GLfloat(screenSize.height())) * 2; |
354 | const GLfloat y2 = 1 - (cr.bottom() / GLfloat(screenSize.height())) * 2; |
355 | QRectF r(QPointF(x1, y1), QPointF(x2, y2)); |
356 | |
357 | draw(r); |
358 | |
359 | if (screen != m_activeScreen) { |
360 | m_activeScreen = screen; |
361 | // Do not want a leftover cursor on the screen the cursor just left. |
362 | update(cursorRect(), true); |
363 | } |
364 | |
365 | break; |
366 | } |
367 | } |
368 | } |
369 | |
370 | // In order to prevent breaking code doing custom OpenGL rendering while |
371 | // expecting the state in the context unchanged, save and restore all the state |
372 | // we touch. The exception is Qt Quick where the scenegraph is known to be able |
373 | // to deal with the changes we make. |
374 | struct StateSaver |
375 | { |
376 | StateSaver() { |
377 | f = QOpenGLContext::currentContext()->functions(); |
378 | vaoHelper = QOpenGLVertexArrayObjectHelper::vertexArrayObjectHelperForContext(QOpenGLContext::currentContext()); |
379 | |
380 | static bool windowsChecked = false; |
381 | static bool shouldSave = true; |
382 | if (!windowsChecked) { |
383 | windowsChecked = true; |
384 | QWindowList windows = QGuiApplication::allWindows(); |
385 | if (!windows.isEmpty() && windows[0]->inherits("QQuickWindow" )) |
386 | shouldSave = false; |
387 | } |
388 | saved = shouldSave; |
389 | if (!shouldSave) |
390 | return; |
391 | |
392 | f->glGetIntegerv(GL_CURRENT_PROGRAM, &program); |
393 | f->glGetIntegerv(GL_TEXTURE_BINDING_2D, &texture); |
394 | f->glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTexture); |
395 | f->glGetIntegerv(GL_FRONT_FACE, &frontFace); |
396 | cull = f->glIsEnabled(GL_CULL_FACE); |
397 | depthTest = f->glIsEnabled(GL_DEPTH_TEST); |
398 | blend = f->glIsEnabled(GL_BLEND); |
399 | f->glGetIntegerv(GL_BLEND_SRC_RGB, blendFunc); |
400 | f->glGetIntegerv(GL_BLEND_SRC_ALPHA, blendFunc + 1); |
401 | f->glGetIntegerv(GL_BLEND_DST_RGB, blendFunc + 2); |
402 | f->glGetIntegerv(GL_BLEND_DST_ALPHA, blendFunc + 3); |
403 | f->glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &arrayBuf); |
404 | if (vaoHelper->isValid()) |
405 | f->glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vao); |
406 | else |
407 | vao = 0; |
408 | for (int i = 0; i < 2; ++i) { |
409 | f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &va[i].enabled); |
410 | f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_SIZE, &va[i].size); |
411 | f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_TYPE, &va[i].type); |
412 | f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &va[i].normalized); |
413 | f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &va[i].stride); |
414 | f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &va[i].buffer); |
415 | f->glGetVertexAttribPointerv(i, GL_VERTEX_ATTRIB_ARRAY_POINTER, &va[i].pointer); |
416 | } |
417 | } |
418 | ~StateSaver() { |
419 | if (saved) { |
420 | f->glUseProgram(program); |
421 | f->glBindTexture(GL_TEXTURE_2D, texture); |
422 | f->glActiveTexture(activeTexture); |
423 | f->glFrontFace(frontFace); |
424 | if (cull) |
425 | f->glEnable(GL_CULL_FACE); |
426 | else |
427 | f->glDisable(GL_CULL_FACE); |
428 | if (depthTest) |
429 | f->glEnable(GL_DEPTH_TEST); |
430 | else |
431 | f->glDisable(GL_DEPTH_TEST); |
432 | if (blend) |
433 | f->glEnable(GL_BLEND); |
434 | else |
435 | f->glDisable(GL_BLEND); |
436 | f->glBlendFuncSeparate(blendFunc[0], blendFunc[1], blendFunc[2], blendFunc[3]); |
437 | f->glBindBuffer(GL_ARRAY_BUFFER, arrayBuf); |
438 | if (vaoHelper->isValid()) |
439 | vaoHelper->glBindVertexArray(vao); |
440 | for (int i = 0; i < 2; ++i) { |
441 | if (va[i].enabled) |
442 | f->glEnableVertexAttribArray(i); |
443 | else |
444 | f->glDisableVertexAttribArray(i); |
445 | f->glBindBuffer(GL_ARRAY_BUFFER, va[i].buffer); |
446 | f->glVertexAttribPointer(i, va[i].size, va[i].type, va[i].normalized, va[i].stride, va[i].pointer); |
447 | } |
448 | } |
449 | } |
450 | QOpenGLFunctions *f; |
451 | QOpenGLVertexArrayObjectHelper *vaoHelper; |
452 | bool saved; |
453 | GLint program; |
454 | GLint texture; |
455 | GLint activeTexture; |
456 | GLint frontFace; |
457 | bool cull; |
458 | bool depthTest; |
459 | bool blend; |
460 | GLint blendFunc[4]; |
461 | GLint vao; |
462 | GLint arrayBuf; |
463 | struct { GLint enabled, type, size, normalized, stride, buffer; GLvoid *pointer; } va[2]; |
464 | }; |
465 | |
466 | void QEglFSCursor::draw(const QRectF &r) |
467 | { |
468 | StateSaver stateSaver; |
469 | |
470 | QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData; |
471 | if (!gfx.program) { |
472 | // one time initialization |
473 | initializeOpenGLFunctions(); |
474 | |
475 | createShaderPrograms(); |
476 | |
477 | if (!gfx.atlasTexture) { |
478 | createCursorTexture(&gfx.atlasTexture, m_cursorAtlas.image); |
479 | |
480 | if (m_cursor.shape != Qt::BitmapCursor) |
481 | m_cursor.useCustomCursor = false; |
482 | } |
483 | } |
484 | |
485 | if (m_cursor.shape == Qt::BitmapCursor && (m_cursor.customCursorPending || m_cursor.customCursorKey != gfx.customCursorKey)) { |
486 | // upload the custom cursor |
487 | createCursorTexture(&gfx.customCursorTexture, m_cursor.customCursorImage); |
488 | m_cursor.useCustomCursor = true; |
489 | m_cursor.customCursorPending = false; |
490 | gfx.customCursorKey = m_cursor.customCursorKey; |
491 | } |
492 | |
493 | GLuint cursorTexture = !m_cursor.useCustomCursor ? gfx.atlasTexture : gfx.customCursorTexture; |
494 | Q_ASSERT(cursorTexture); |
495 | |
496 | gfx.program->bind(); |
497 | |
498 | const GLfloat x1 = r.left(); |
499 | const GLfloat x2 = r.right(); |
500 | const GLfloat y1 = r.top(); |
501 | const GLfloat y2 = r.bottom(); |
502 | const GLfloat cursorCoordinates[] = { |
503 | x1, y2, |
504 | x2, y2, |
505 | x1, y1, |
506 | x2, y1 |
507 | }; |
508 | |
509 | const GLfloat s1 = m_cursor.textureRect.left(); |
510 | const GLfloat s2 = m_cursor.textureRect.right(); |
511 | const GLfloat t1 = m_cursor.textureRect.top(); |
512 | const GLfloat t2 = m_cursor.textureRect.bottom(); |
513 | const GLfloat textureCoordinates[] = { |
514 | s1, t2, |
515 | s2, t2, |
516 | s1, t1, |
517 | s2, t1 |
518 | }; |
519 | |
520 | glActiveTexture(GL_TEXTURE0); |
521 | glBindTexture(GL_TEXTURE_2D, cursorTexture); |
522 | |
523 | if (stateSaver.vaoHelper->isValid()) |
524 | stateSaver.vaoHelper->glBindVertexArray(0); |
525 | |
526 | glBindBuffer(GL_ARRAY_BUFFER, 0); |
527 | |
528 | gfx.program->enableAttributeArray(0); |
529 | gfx.program->enableAttributeArray(1); |
530 | gfx.program->setAttributeArray(0, cursorCoordinates, 2); |
531 | gfx.program->setAttributeArray(1, textureCoordinates, 2); |
532 | |
533 | gfx.program->setUniformValue(gfx.textureEntry, 0); |
534 | gfx.program->setUniformValue(gfx.matEntry, m_rotationMatrix); |
535 | |
536 | glDisable(GL_CULL_FACE); |
537 | glFrontFace(GL_CCW); |
538 | glEnable(GL_BLEND); |
539 | glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); |
540 | glDisable(GL_DEPTH_TEST); // disable depth testing to make sure cursor is always on top |
541 | |
542 | glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
543 | |
544 | gfx.program->disableAttributeArray(0); |
545 | gfx.program->disableAttributeArray(1); |
546 | gfx.program->release(); |
547 | } |
548 | |
549 | QT_END_NAMESPACE |
550 | |